了解 JavaScript 中的箭头函数

作者选择了 COVID-19 救援基金作为 Write for Donations计划的一部分接受捐款。

介绍

2015 版 ECMAScript 规格 (ES6)将 _arrow 函数表达式添加到 JavaScript语言中。

箭头函数以多种方式不同于传统函数,包括如何确定其范围和如何表达其语法。因此,箭头函数在将函数作为参数转移到更高序列的函数时尤为有用,例如当您使用 嵌入式迭代器方法旋转一个 数组时)。

在本文中,您将审查函数声明和表达式,了解传统函数表达式和箭头函数表达式之间的差异,了解有关箭头函数的语法范围,并探索与箭头函数允许的一些语法缩写。

功能定义

在深入了解箭头函数表达式的特点之前,本教程将简要审查传统的JavaScript函数,以便稍后更好地展示箭头函数的独特方面。

本系列中早些时候的 How To Define Functions in JavaScript 教程介绍了 function declarationsfunction 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

这些示例表明,在内置的数组方法中使用箭头函数(如 forEachmapfilter、和 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中的变量,范围和提升

Published At
Categories with 技术
comments powered by Disqus