13. 箭头函数
- 13.1. 概述
- 13.2. 传统函数由于 `this` 的原因,不适合作为非方法函数
- 13.2.1. 解决方案 1:`that = this`
- 13.2.2. 解决方案 2:指定 `this` 的值
- 13.2.3. 解决方案 3:`bind(this)`
- 13.2.4. ECMAScript 6 解决方案:箭头函数
- 13.3. 箭头函数语法
- 13.4. 词法变量
- 13.4.1. 传播变量值:静态与动态
- 13.4.2. 箭头函数中的词法变量
- 13.5. 语法陷阱
- 13.5.1. 箭头函数的绑定优先级很低
- 13.5.2. 箭头函数参数后不能换行
- 13.5.3. 不能使用语句作为表达式主体
- 13.5.4. 返回对象字面量
- 13.6. 立即执行的箭头函数
- 13.6.1. 分号
- 13.6.2. 带有代码块主体的箭头函数的括号
- 13.6.3. 带有表达式主体的箭头函数的括号
- 13.7. 箭头函数与 `bind()`
- 13.7.1. 提取方法
- 13.7.2. 通过参数传递 `this`
- 13.7.3. 部分求值
- 13.8. 箭头函数与普通函数
- 13.9. 常见问题解答:箭头函数
- 13.9.1. 为什么 ES6 中有“胖”箭头函数 (`=>`),却没有“瘦”箭头函数 (`->`)?
13.1 概述
箭头函数有两个优点。
首先,它们比传统的函数表达式更简洁
其次,它们的 `this` 是从周围环境中获取的(*词法作用域*)。因此,你不再需要 `bind()` 或 `that = this` 了。
以下变量在箭头函数内部都是词法变量
arguments
super
this
new.target
13.2 传统函数由于 `this` 的原因,不适合作为非方法函数
在 JavaScript 中,传统函数可以用作
- 非方法函数
- 方法
- 构造函数
这些角色之间存在冲突:由于角色 2 和 3,函数始终拥有自己的 `this`。但这会阻止你从回调函数(角色 1)内部访问例如周围方法的 `this`。
你可以在以下 ES5 代码中看到这一点
在 C 行,我们想访问 `this.prefix`,但不能,因为 B 行函数的 `this` 遮蔽了 A 行方法的 `this`。在严格模式下,非方法函数中的 `this` 是 `undefined`,这就是为什么我们在使用 `Prefixer` 时会出错
在 ECMAScript 5 中,有三种方法可以解决这个问题。
13.2.1 解决方案 1:`that = this`
你可以将 `this` 赋值给一个不会被遮蔽的变量。这就是下面 A 行所做的
现在 `Prefixer` 可以按预期工作了
13.2.2 解决方案 2:指定 `this` 的值
一些数组方法有一个额外的参数,用于指定在调用回调函数时 `this` 应该具有的值。这就是下面 A 行中的最后一个参数。
13.2.3 解决方案 3:`bind(this)`
你可以使用 `bind()` 方法将一个 `this` 由其调用方式(通过 `call()`、函数调用、方法调用等)决定的函数转换为一个 `this` 始终为相同固定值的函数。这就是我们在下面 A 行所做的。
13.2.4 ECMAScript 6 解决方案:箭头函数
箭头函数的工作原理与解决方案 3 非常相似。但是,最好将它们视为一种不会在词法上遮蔽 `this` 的新型函数。也就是说,它们不同于普通函数(你甚至可以说它们的功能更少)。它们不是绑定了 `this` 的普通函数。
使用箭头函数,代码如下所示。
为了完全使用 ES6 语法,你可以使用类和更紧凑的箭头函数变体
在 A 行,我们通过调整箭头函数的两个部分来节省了一些字符
- 如果只有一个参数,并且该参数是一个标识符,则可以省略括号。
- 箭头后面的表达式会导致返回该表达式的值。
13.3 箭头函数语法
选择“胖”箭头 `=>`(而不是瘦箭头 `->`) 是为了与 CoffeeScript 兼容,CoffeeScript 的胖箭头函数非常相似。
指定参数
指定函数体
语句块的行为类似于普通的函数体。例如,你需要使用 `return` 来返回值。对于表达式主体,表达式始终是隐式返回的。
请注意,带有表达式主体的箭头函数可以大大减少代码的冗余。比较
13.3.1 省略单个参数周围的括号
只有当参数由单个标识符组成时,才能省略参数周围的括号
一旦有其他任何内容,你就必须输入括号,即使只有一个参数。例如,如果你解构单个参数,则需要括号
如果单个参数具有默认值(`undefined` 会触发默认值!),则需要括号
13.4 词法变量
13.4.1 传播变量值:静态与动态
以下是传播变量值的两种方式。
首先,静态(词法):变量的可访问性由程序的结构决定。在作用域中声明的变量在其嵌套的所有作用域中都可访问(除非被遮蔽)。例如
其次,动态:变量值可以通过函数调用传播。例如
13.4.2 箭头函数中的词法变量
`this` 的来源是箭头函数的一个重要区别
- 传统函数具有 *动态 `this`*;其值由其调用方式决定。
- 箭头函数具有 *词法 `this`*;其值由周围的作用域决定。
其值由词法作用域决定的变量的完整列表是
arguments
super
this
new.target
13.5 语法陷阱
有一些与语法相关的细节有时会让你感到困惑。
13.5.1 箭头函数的绑定优先级很低
如果你将 `=>` 视为运算符,你可以说它的优先级很低,绑定得很松散。这意味着如果它与其他运算符冲突,其他运算符通常会获胜。
这样做的原因是允许表达式主体“粘在一起”
换句话说,我们希望 `=>` 在与 `===` 和 `?` 的竞争中失败。我们希望它被解释如下
如果 `=>` 在两者中都获胜,它将如下所示
如果 `=>` 在与 `===` 的竞争中失败,但在与 `?` 的竞争中获胜,它将如下所示
因此,如果箭头函数与其他运算符竞争,你通常必须将它们括在括号中。例如
另一方面,你可以使用 `typeof` 作为表达式主体,而无需将其括在括号中
13.5.2 箭头函数参数后不能换行
ES6 禁止在箭头函数的参数定义和箭头之间换行
参数定义 *内部* 的换行是可以的
此限制的理由是,它为将来使用“无头”箭头函数保留了选择(在定义具有零个参数的箭头函数时,你可以省略括号)。
13.5.3 不能使用语句作为表达式主体
13.5.3.1 表达式与语句
快速回顾(有关详细信息,请参阅“Speaking JavaScript”)
表达式产生(被求值为)值。例子
语句做事情。例子
大多数表达式1 都可以用作语句,只需在语句位置提及它们即可
13.5.3.2 箭头函数的函数体
如果表达式是箭头函数的函数体,则不需要大括号
但是,语句必须放在大括号中
13.5.4 返回对象字面量
JavaScript 语法中的某些部分是不明确的。以以下代码为例。
它可能是
- 具有单个属性 `bar` 的对象字面量。
- 带有标签 `bar` 和表达式语句 `123` 的代码块。
鉴于箭头函数的函数体可以是表达式或语句,如果你希望对象字面量是表达式主体,则必须将其括在括号中
为了进行比较,这是一个函数体为代码块的箭头函数
13.6 立即执行的箭头函数
还记得立即执行的函数表达式 (IIFE) 吗?它们如下所示,用于在 ECMAScript 5 中模拟块级作用域和返回值的代码块
如果使用立即执行的箭头函数 (IIAF),则可以节省一些字符
13.6.1 分号
与 IIFE 类似,你应该使用分号终止 IIAF(或使用等效措施),以避免将两个连续的 IIAF 解释为函数调用(第一个作为函数,第二个作为参数)。
13.6.2 带有代码块主体的箭头函数的括号
即使 IIAF 具有代码块主体,你也必须将其括在括号中,因为它不能(直接)作为函数调用。这种语法约束的原因是为了与函数体为表达式的箭头函数保持一致(如下所述)。
因此,括号必须放在箭头函数周围。相反,对于 IIFE,你可以选择将括号放在整个表达式周围
或者只放在函数表达式周围
考虑到箭头函数的工作原理,从现在开始,应该优先考虑后一种加括号的方式。
13.6.3 带有表达式主体的箭头函数的括号
如果你想理解为什么不能在箭头函数后面直接加括号来调用它,你必须先了解表达式体的运作方式:表达式体后面的括号应该属于表达式的一部分,而不是整个箭头函数的调用。这与箭头函数的松散绑定有关,正如前面章节所解释的那样。
让我们看一个例子
这应该被解释为
而不是
延伸阅读:关于可调用实体的章节中的一节提供了更多关于在 ES6 中使用 IIFE 和 IIAF 的信息。剧透:你很少需要它们,因为 ES6 通常提供更好的替代方案。
13.7 箭头函数与 bind()
ES6 箭头函数通常是 Function.prototype.bind()
的一个引人注目的替代方案。
如果要将提取的方法作为回调函数使用,则必须指定固定的 this
,否则它将作为普通函数被调用(并且 this
将是 undefined
或全局对象)。例如
另一种方法是使用箭头函数
13.7.2 通过参数传递 this
以下代码演示了一个巧妙的技巧:对于某些方法,你不需要 bind()
来绑定回调函数,因为它们允许你通过额外的参数指定 this
的值。filter()
就是这样一种方法
但是,如果使用箭头函数,这段代码更容易理解
13.7.3 部分求值
bind()
使你能够进行部分求值,你可以通过填充现有函数的参数来创建新函数
同样,我发现箭头函数更容易理解
13.8 箭头函数与普通函数
箭头函数与普通函数只有两个方面的不同
- 以下结构是词法上的:
arguments
、super
、this
、new.target
- 它不能用作构造函数:普通函数通过内部方法
[[Construct]]
和属性 prototype
支持 new
。箭头函数两者都没有,这就是 new (() => {})
会抛出错误的原因。
除此之外,箭头函数和普通函数之间没有其他可观察到的差异。例如,typeof
和 instanceof
会产生相同的结果
有关何时使用箭头函数以及何时使用传统函数的更多信息,请参阅关于可调用实体的章节。
13.9 常见问题解答:箭头函数
13.9.1 为什么 ES6 中有“胖”箭头函数 (=>
),却没有“瘦”箭头函数 (->
)?
ECMAScript 6 为具有词法 this
的函数提供了语法,即所谓的*箭头函数*。但是,它没有为具有动态 this
的函数提供箭头语法。这种省略是故意的;方法定义涵盖了瘦箭头的绝大多数用例。如果你真的需要动态 this
,你仍然可以使用传统的函数表达式。