作者选择了 COVID-19 救援基金作为 Write for Donations计划的一部分接受捐款。
介绍
2015 版 ECMAScript 规格 (ES6)将 _arrow 函数表达式添加到 JavaScript语言中。
箭头函数以多种方式不同于传统函数,包括如何确定其范围和如何表达其语法。因此,箭头函数在将函数作为参数转移到更高序列的函数时尤为有用,例如当您使用 嵌入式迭代器方法旋转一个 数组时)。
在本文中,您将审查函数声明和表达式,了解传统函数表达式和箭头函数表达式之间的差异,了解有关箭头函数的语法范围,并探索与箭头函数允许的一些语法缩写。
功能定义
在深入了解箭头函数表达式的特点之前,本教程将简要审查传统的JavaScript函数,以便稍后更好地展示箭头函数的独特方面。
本系列中早些时候的 How To Define Functions in JavaScript 教程介绍了 function declarations 和 function expressions 的概念. 函数声明是用function
关键字写的命名函数。
以下是返回两个参数的总
函数的示例:
1function sum(a, b) {
2 return a + b
3}
您可以执行总
函数,然后在声明函数由于升值之前:
1sum(1, 2)
2
3function sum(a, b) {
4 return a + b
5}
运行此代码将产生以下输出:
1[secondary_label Output]
23
您可以通过登录函数本身来找到函数的名称:
1console.log(sum)
这将返回函数,以及其名称:
1[secondary_label Output]
2ƒ sum(a, b) {
3 return a + b
4}
函数表达式是一个函数,它没有预装到执行背景中,并且只有在代码遇到它时才能运行。
在本示例中,将相同的总
函数写成匿名函数表达式:
1const sum = function (a, b) {
2 return a + b
3}
现在您已将匿名函数分配给总
常数,试图在声明之前执行该函数会导致错误:
1sum(1, 2)
2
3const sum = function (a, b) {
4 return a + b
5}
运行这将给出:
1[secondary_label Output]
2Uncaught ReferenceError: Cannot access 'sum' before initialization
另外,请注意,该函数没有命名标识符,以说明这一点,请写入与总
相同的匿名函数,然后将总
登录到控制台:
1const sum = function (a, b) {
2 return a + b
3}
4
5console.log(sum)
这将向你展示如下:
1[secondary_label Output]
2ƒ (a, b) {
3 return a + b
4}
总
的值是一个匿名函数,而不是一个命名函数。
您可以用函数
的关键字命名函数表达式,但这在实践中并不受欢迎。
考虑下面的函数,它使用一个 if
语句 以丢失函数参数时发出错误:
1const sum = function namedSumFunction(a, b) {
2 if (!a || !b) throw new Error('Parameters are required.')
3
4 return a + b
5}
6
7sum();
突出的部分给函数一个名字,然后函数使用 ** 或** 或 的运算器,如果任何参数都缺少,就会引发错误 object。
运行此代码将为您提供以下内容:
1[secondary_label Output]
2Uncaught Error: Parameters are required.
3 at namedSumFunction (<anonymous>:3:23)
4 at <anonymous>:1:1
在这种情况下,命名函数会让你快速了解错误所在。
arrow function expression是用脂肪箭头
语法(=>
)编写的匿名函数表达式。
用箭头函数语法重写sum
函数:
1const sum = (a, b) => {
2 return a + b
3}
与传统的函数表达式一样,箭头函数不会被举起,所以在宣布它们之前,您无法调用它们,它们也总是匿名,没有办法命名箭头函数。
箭头函数行为和语法
箭头函数在它们的工作方式中有几个重要的区别,这将它们与传统函数区分开来,以及一些语法增强。最大的功能差异是箭头函数没有自己的这个
绑定或原型,不能用作构建器。箭头函数也可以写作为传统函数的更紧凑的替代品,因为它们赋予了在参数周围排列的能力,并添加了具有暗示回报的简洁函数体的概念。
在本节中,您将通过示例来说明这些情况。
经典这
该文章(https://www.digitalocean.com/community/conceptual_articles/understanding-this-bind-call-and-apply-in-javascript)解释了如何这
工作,以及如何这
可以根据程序是否在全球范围内使用它,作为对象中的方法,作为函数或类的构造者,或作为一个 DOM事件处理器来暗示。
箭头函数具有 _lexical this
,这意味着 this
的值由周围范围(语法环境)决定。
下面的示例将展示传统和箭头函数如何处理这个
之间的差异. 在下面的printNumbers
对象中,有两个属性:句子
和数字
。
1const printNumbers = {
2 phrase: 'The current value is:',
3 numbers: [1, 2, 3, 4],
4
5 loop() {
6 this.numbers.forEach(function (number) {
7 console.log(this.phrase, number)
8 })
9 },
10}
人们可以期望循环
函数在每个 iteraton 上打印循环中的字符串和当前数字,然而,在运行该函数的结果中,短语
实际上是未定义的
:
1printNumbers.loop()
这将给出如下:
1[secondary_label Output]
2undefined 1
3undefined 2
4undefined 3
5undefined 4
正如本文所示,this.phrase
是未定义的,这表明this
在被转移到forEach
方法的匿名函数内(https://andsky.com/tech/tutorials/how-to-use-array-methods-in-javascript-iteration-methods#foreach())并不指printNumbers
对象,这是因为传统的函数不会从printNumbers
对象的环境范围中确定其this
值。
在早期版本的JavaScript中,你不得不使用绑定
方法,它明确设置了这是
,这种模式在一些早期版本的框架中,例如 React,在ES6的出现之前,常见。
使用绑定
来修复该函数:
1const printNumbers = {
2 phrase: 'The current value is:',
3 numbers: [1, 2, 3, 4],
4
5 loop() {
6 // Bind the `this` from printNumbers to the inner forEach function
7 this.numbers.forEach(
8 function (number) {
9 console.log(this.phrase, number)
10 }.bind(this),
11 )
12 },
13}
14
15printNumbers.loop()
这将带来预期的结果:
1[secondary_label Output]
2The current value is: 1
3The current value is: 2
4The current value is: 3
5The current value is: 4
箭头函数提供了更直接的方式来处理这个问题,因为它们的这个
值是根据语法范围来确定的,所以被称为forEach
的内部函数现在可以访问外部打印数字
对象的属性,如下所示:
1const printNumbers = {
2 phrase: 'The current value is:',
3 numbers: [1, 2, 3, 4],
4
5 loop() {
6 this.numbers.forEach((number) => {
7 console.log(this.phrase, number)
8 })
9 },
10}
11
12printNumbers.loop()
这将带来预期的结果:
1[secondary_label Output]
2The current value is: 1
3The current value is: 2
4The current value is: 3
5The current value is: 4
这些示例表明,在内置的数组方法中使用箭头函数(如 forEach
、 map
、 filter
、和 reduce
可以更直观,更容易阅读,使这种策略更有可能满足预期。
箭头函数作为对象方法
虽然箭头函数作为参数函数转移到数组方法非常出色,但它们作为对象方法并不有效,因为它们用于这
的语法范围。
1const printNumbers = {
2 phrase: 'The current value is:',
3 numbers: [1, 2, 3, 4],
4
5 loop: () => {
6 this.numbers.forEach((number) => {
7 console.log(this.phrase, number)
8 })
9 },
10}
在此对象方法的情况下,这
应该是指printNumbers
对象的属性和方法,但是,由于一个对象不会创建一个新的语法范围,所以箭头函数会超越对象以这
的值。
使用loop()
方法:
1printNumbers.loop()
这将给出如下:
1[secondary_label Output]
2Uncaught TypeError: Cannot read property 'forEach' of undefined
由于该对象不会创建一个词汇范围,所以箭头函数方法在本示例中寻找外部范围中的This
(Window
)(LINK0
) 因为数字
属性在Window
对象上不存在,因此会引发错误。
箭头函数没有构建者
或原型
本系列早些时候的《理解JavaScript的原型和继承》(LINK0)教程解释说,函数和类具有一个原型
属性,这就是JavaScript用来作为克隆和继承的蓝图。
为了说明这一点,创建一个函数并登录自动分配的原型
属性:
1function myFunction() {
2 this.value = 5
3}
4
5// Log the prototype property of myFunction
6console.log(myFunction.prototype)
这将打印以下内容到控制台:
1[secondary_label Output]
2{constructor: ƒ}
这表明在原型
属性中有一个具有构建
的对象,这允许您使用新
关键字来创建该函数的实例:
1const instance = new myFunction()
2
3console.log(instance.value)
这将产生您在首次声明函数时定义的值
属性的值:
1[secondary_label Output]
25
相反,箭头函数没有原型
属性. 创建一个新的箭头函数并尝试登录其原型:
1const myArrowFunction = () => {}
2
3// Attempt to log the prototype property of myArrowFunction
4console.log(myArrowFunction.prototype)
这将给出如下:
1[secondary_label Output]
2undefined
由于缺少的原型
属性,因此新
关键字不可用,您无法从箭头函数构建一个实例:
1const arrowInstance = new myArrowFunction()
2
3console.log(arrowInstance)
这会导致以下错误:
1[secondary_label Output]
2Uncaught TypeError: myArrowFunction is not a constructor
这与我们之前的例子是一致的:由于箭头函数没有自己的这个
值,因此您将无法使用箭头函数作为构建器。
正如这里所示,箭头函数有很多微妙的变化,使它们与ES5和以前的传统函数不同。 还有一些可选的语法更改,使写箭头函数更快,更不易说话。
暗示回归
一个传统函数的体内包含在一个区块中,使用弯曲的支架{}
,并在代码遇到一个返回
的关键字时结束。
1const sum = (a, b) => {
2 return a + b
3}
箭头函数引入 concise body syntax,或 implicit return. 这允许忽略弯曲的轴承和返回
关键字。
1const sum = (a, b) => a + b
默认返回对于在地图
,过滤器
和其他常见的数组方法中创建简短的单行操作有用。 请注意,必须忽略两个字符串和返回
关键字。
在返回对象的情况下,语法要求你将对象字面包裹在中,否则,将被视为一个函数体,不会计算一个返回
值。
为了说明这一点,请参见以下例子:
1const sum = (a, b) => ({result: a + b})
2
3sum(1, 2)
这将产生以下产出:
1[secondary_label Output]
2{result: 3}
排除围绕单个参数的关节
另一个有用的语法增强是能够在函数中删除一个参数周围的关节,在下面的示例中,平方
函数只在一个参数上运行,即x
:
1const square = (x) => x * x
因此,您可以忽略参数周围的关节,并且它将以相同的方式工作:
1const square = x => x * x
2
3square(10)
这将给出如下:
1[secondary_label Output]
2100
请注意,如果一个函数没有参数,则需要对接:
1const greet = () => 'Hello!'
2
3greet()
调用greet()
将如下工作:
1[secondary_label Output]
2'Hello!'
有些代码库选择在可能的情况下排除,而其他代码库则选择始终在参数周围保留,特别是在使用 TypeScript并需要有关每个变量和参数的更多信息的代码库中。
结论
在本文中,您审查了传统函数和函数声明和函数表达之间的差异. 您了解到箭头函数总是匿名,没有原型
或构建者
,不能与新
关键字一起使用,并通过语法范围确定这
的值。
有关基本函数的概述,请阅读 如何在JavaScript中定义函数。 有关JavaScript中的范围和提升概念的更多信息,请阅读 理解JavaScript中的变量,范围和提升。