箭头函数表达式
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
箭头函数表达式的语法比传统的函数表达式更简洁,但在语义上有一些差异,在用法上也有一些限制:
尝试一下
语法
() => expression
param => expression
(param) => expression
(param1, paramN) => expression
() => {
statements
}
param => {
statements
}
(param1, paramN) => {
statements
}
参数部分支持剩余参数、默认参数和解构赋值,并且始终需要使用括号:
(a, b, ...r) => expression
(a = 400, b = 20, c) => expression
([a, b] = [10, 20]) => expression
({ a, b } = { a: 10, b: 20 }) => expression
箭头函数可以是 async
的,方法是在表达式前加上 async
关键字。
async param => expression
async (param1, param2, ...paramN) => {
statements
}
描述
让我们逐步将传统的匿名函数分解为最简单的箭头函数。每一步都是一个有效的箭头函数。
备注:传统函数表达式和箭头函数除了语法上的区别外,还有更多的不同。我们将在接下来的几个小节中详细介绍它们的行为差异。
// 传统匿名函数
(function (a) {
return a + 100;
});
// 1. 移除“function”,并将箭头放置于参数和函数体起始大括号之间
(a) => {
return a + 100;
};
// 2. 移除代表函数体的大括号和“return”——返回值是隐含的
(a) => a + 100;
// 3. 移除参数周围的括号
a => a + 100;
在上面的示例中,参数周围的括号和函数体周围的大括号都可以省略。但是,只有在某些情况下才能省略。
只有当函数只有一个简单参数时,才能省略括号。如果函数有多个参数、无参数、默认参数、重组参数或其余参数,则需要在参数列表周围加上括号。
// 传统匿名函数
(function (a, b) {
return a + b + 100;
});
// 箭头函数
(a, b) => a + b + 100;
const a = 4;
const b = 2;
// 传统无参匿名函数
(function () {
return a + b + 100;
});
// 无参箭头函数
() => a + b + 100;
只有当函数直接返回表达式时,才可以省略大括号。如果函数体有额外的处理,则大括号是必需的,return
关键字也是必需的。箭头函数无法猜测函数体返回什么或何时返回。
// 传统匿名函数
(function (a, b) {
const chuck = 42;
return a + b + chuck;
});
// 箭头函数
(a, b) => {
const chuck = 42;
return a + b + chuck;
};
箭头函数总是未命名的。如果箭头函数需要调用自身,请使用具名函数表达式。也可以将箭头函数赋值给一个变量,这样它就有了名字。
// 传统函数
function bob(a) {
return a + 100;
}
// 箭头函数
const bob2 = (a) => a + 100;
函数体
箭头函数既可以使用表达式体(expression body),也可以使用通常的块体(block body)。
在表达式体中,只需指定一个表达式,它将成为隐式返回值。在块体中,必须使用显式的 return
语句。
const func = (x) => x * x;
// 表达式体语法,隐含返回值
const func2 = (x, y) => {
return x + y;
};
// 块体语法,需要明确返回值
使用表达式体语法 (params) => { object: literal }
返回对象字面量时,不能按预期工作。
const func = () => { foo: 1 };
// 调用 func() 会返回 undefined!
const func2 = () => { foo: function () {} };
// SyntaxError: function statement requires a name
const func3 = () => { foo() {} };
// SyntaxError: Unexpected token '{'
这是因为只有当箭头后面的标记不是左括号时,JavaScript 才会将箭头函数视为表达式体,因此括号({})内的代码会被解析为一系列语句,其中 foo
是标签,而不是对象文字中的键。
要解决这个问题,可以用括号将对象字面量包装起来:
const func = () => ({ foo: 1 });
不能用作方法
箭头函数表达式只能用于非方法函数,因为它们没有自己的 this
。让我们看看将它们用作方法时会发生什么:
"use strict";
const obj = {
i: 10,
b: () => console.log(this.i, this),
c() {
console.log(this.i, this);
},
};
obj.b(); // 输出 undefined, Window { /* … */ }(或全局对象)
obj.c(); // 输出 10, Object { /* … */ }
另外一个示例涉及到 Object.defineProperty()
:
"use strict";
const obj = {
a: 10,
};
Object.defineProperty(obj, "b", {
get: () => {
console.log(this.a, typeof this.a, this); // undefined 'undefined' Window { /* … */ }(或全局对象)
return this.a + 10; // 代表全局对象 'Window',故 `this.a' 返回 'undefined'
},
});
由于类体具有 this
上下文,因此作为类字段的箭头函数会关闭类的 this
上下文,箭头函数体中的 this
将正确指向实例(对于静态字段来说是类本身)。但是,由于它是一个闭包,而不是函数本身的绑定,因此 this
的值不会根据执行上下文而改变。
class C {
a = 1;
autoBoundMethod = () => {
console.log(this.a);
};
}
const c = new C();
c.autoBoundMethod(); // 1
const { autoBoundMethod } = c;
autoBoundMethod(); // 1
// 如果这是普通方法,此时应该是 undefined
箭头函数属性通常被称作“自动绑定方法”,因为它与普通方法的等价性相同:
class C {
a = 1;
constructor() {
this.method = this.method.bind(this);
}
method() {
console.log(this.a);
}
}
备注:类字段是在实例(instance)上定义的,而不是在原型(prototype)上定义的,因此每次创建实例都会创建一个新的函数引用并分配一个新的闭包,这可能会导致比普通非绑定方法更多的内存使用。
出于类似原因,call()
、apply()
和 bind()
方法在箭头函数上调用时不起作用,因为箭头函数是根据箭头函数定义的作用域来建立 this
的,而 this
值不会根据函数的调用方式而改变。
没有参数绑定
箭头函数没有自己的 arguments
对象。因此,在本例中,arguments
是对外层作用域参数的引用:
function foo(n) {
const f = () => arguments[0] + n; // foo 的隐式参数绑定。arguments[0] 为 n
return f();
}
foo(3); // 3 + 3 = 6
备注:在严格模式下不能声明名为 arguments
的变量,因此上面的代码会出现语法错误。这使得 arguments
的范围效应更容易理解。
在大多数情况下,使用剩余参数是比使用 arguments
对象更好的选择。
function foo(n) {
const f = (...args) => args[0] + n;
return f(10);
}
foo(1); // 11
不能用作构造函数
不能用作生成器
箭头函数的主体中不能使用 yield
关键字(除非在箭头函数进一步嵌套的生成器函数中使用)。因此,箭头函数不能用作生成器。
箭头前换行
箭头函数的参数和箭头之间不能换行。
const func = (a, b, c)
=> 1;
// SyntaxError: Unexpected token '=>'
为便于格式化,可在箭头后换行,或在函数体周围使用括号/花括号,如下图所示。也可以在参数之间换行。
const func = (a, b, c) =>
1;
const func2 = (a, b, c) => (
1
);
const func3 = (a, b, c) => {
return 1;
};
const func4 = (
a,
b,
c,
) => 1;
箭头的优先级
虽然箭头函数中的箭头不是运算符,但与普通函数相比,箭头函数具有特殊的解析规则,与运算符优先级的交互方式不同。
let callback;
callback = callback || () => {};
// SyntaxError: invalid arrow-function arguments
由于 =>
的优先级低于大多数运算符,因此需要使用括号来避免 callback || ()
被解析为箭头函数的参数列表。
callback = callback || (() => {});
示例
使用箭头函数
// 空的箭头函数返回 undefined
const empty = () => {};
(() => "foobar")();
// 返回 "foobar"
// 这是一个立即执行函数表达式
const simple = (a) => (a > 15 ? 15 : a);
simple(16); // 15
simple(10); // 10
const max = (a, b) => (a > b ? a : b);
// 更方便进行数组的过滤、映射等工作
const arr = [5, 6, 13, 0, 1, 18, 23];
const sum = arr.reduce((a, b) => a + b);
// 66
const even = arr.filter((v) => v % 2 === 0);
// [6, 0, 18]
const double = arr.map((v) => v * 2);
// [10, 12, 26, 0, 2, 36, 46]
// 更简明的 promise 链
promise
.then((a) => {
// …
})
.then((b) => {
// …
});
// 无参数箭头函数在视觉上容易分析
setTimeout(() => {
console.log("我发生更早");
setTimeout(() => {
// 深层次代码
console.log("我发生更晚");
}, 1);
}, 1);
使用 call、bind 和 apply
call()
、apply()
和 bind()
方法与传统函数一样按照预期工作,因为我们为每个方法建立了作用域:
const obj = {
num: 100,
};
// 在 globalThis 上设置“num”,以显示它如何没有被使用到。
globalThis.num = 42;
// 对“this”进行操作的简单传统函数
const add = function (a, b, c) {
return this.num + a + b + c;
};
console.log(add.call(obj, 1, 2, 3)); // 106
console.log(add.apply(obj, [1, 2, 3])); // 106
const boundAdd = add.bind(obj);
console.log(boundAdd(1, 2, 3)); // 106
对于箭头函数,由于我们的 add
函数基本上是在 globalThis
(全局)作用域上创建的,因此它会假定 this
就是 globalThis
。
const obj = {
num: 100,
};
// 在 globalThis 上设置“num”,以显示它是如何被接收到的。
globalThis.num = 42;
// 箭头函数
const add = (a, b, c) => this.num + a + b + c;
console.log(add.call(obj, 1, 2, 3)); // 48
console.log(add.apply(obj, [1, 2, 3])); // 48
const boundAdd = add.bind(obj);
console.log(boundAdd(1, 2, 3)); // 48
使用箭头函数的最大好处可能是在使用 setTimeout()
和 EventTarget.prototype.addEventListener()
等方法时,这些方法通常需要某种闭包、call()
、apply()
或 bind()
,以确保函数在适当的作用域中执行。
对于传统的函数表达式,类似这样的代码并不能像预期的那样工作:
const obj = {
count: 10,
doSomethingLater() {
setTimeout(function () {
// 此函数在 window 作用域下执行
this.count++;
console.log(this.count);
}, 300);
},
};
obj.doSomethingLater(); // 输出“NaN”,因为“count”属性不在 window 作用域下。
有了箭头函数,this
作用域更容易被保留:
const obj = {
count: 10,
doSomethingLater() {
// 该方法语法将“this”与“obj”上下文绑定。
setTimeout(() => {
// 由于箭头函数没有自己的绑定,
// 而 setTimeout(作为函数调用)本身也不创建绑定,
// 因此使用了外部方法的“obj”上下文。
this.count++;
console.log(this.count);
}, 300);
},
};
obj.doSomethingLater(); // 输出 11
规范
Specification |
---|
ECMAScript Language Specification # sec-arrow-function-definitions |
浏览器兼容性
BCD tables only load in the browser
参见
- 函数指南
- 函数参考
function
function
表达式- 深入了解 ES6:箭头函数,载于 hacks.mozilla.org(2015)