理解 JavaScript 中的箭头函数

作者选择了COVID-19 救济基金来接受捐赠,作为Write for DOnations计划的一部分。

介绍

ECMAScript 规范 (ES6)2015 版JavaScript语言添加了箭头函数表达式箭头函数是一种编写匿名函数表达式的新方法,类似于其他一些编程语言(如Python)中的lambda 函数

箭头函数在许多方面与传统函数不同,包括确定其作用域的方式以及如何表达其语法。因此,在将函数作为参数传递给高阶函数时,例如在使用内置迭代器方法循环数组时,箭头函数特别有用它们的语法缩写还可以让您提高代码的可读性。

在本文中,您将回顾函数声明和表达式,了解传统函数表达式和箭头函数表达式之间的区别,了解与箭头函数相关的词法作用域,并探索箭头函数允许的一些语法速记。

定义函数

在深入研究箭头函数表达式的细节之前,本教程将简要回顾传统的 JavaScript 函数,以便稍后更好地展示箭头函数的独特方面。

本系列前面如何在 JavaScript 中定义函数教程介绍了函数声明函数表达式的概念函数声明是用function关键字编写的命名函数函数声明在任何代码运行之前加载到执行上下文中。这称为hoisting,这意味着您可以在声明之前使用该函数。

下面是一个sum返回两个参数之和的函数示例

function sum(a, b) {
  return a + b
}

sum由于提升,您可以在声明函数之前执行该函数:

sum(1, 2)

function sum(a, b) {
  return a + b
}

运行此代码将提供以下输出:

Output
3

您可以通过记录函数本身来找到函数的名称:

console.log(sum)

这将返回函数及其名称:

Output
ƒ sum(a, b) { return a + b }

函数表达式是一个没有预加载到执行上下文中的函数,只有在代码遇到它时才会运行。函数表达式通常分配给一个变量,并且可以是匿名的,这意味着函数没有名称。

在本例中,编写与sum匿名函数表达式相同的函数:

const sum = function (a, b) {
  return a + b
}

您现在已将匿名函数分配给sum常量。在函数声明之前尝试执行它会导致错误:

sum(1, 2)

const sum = function (a, b) {
  return a + b
}

运行这将给出:

Output
Uncaught ReferenceError: Cannot access 'sum' before initialization

另请注意,该函数没有命名标识符。为了说明这一点,编写分配给 的相同匿名函数sum,然后登录sum到控制台:

const sum = function (a, b) {
  return a + b
}

console.log(sum)

这将显示以下内容:

Output
ƒ (a, b) { return a + b }

的值sum是匿名函数,而不是命名函数。

您可以命名用function关键字编写的函数表达式,但这在实践中并不流行。您可能想要命名函数表达式的一个原因是使错误堆栈跟踪更易于调试。

考虑以下函数,如果缺少函数参数,它使用if语句抛出错误:

const sum = function namedSumFunction(a, b) {
  if (!a || !b) throw new Error('Parameters are required.')

  return a + b
}

sum();

突出显示的部分为函数命名,然后函数使用or ||运算符在缺少任何一个参数时抛出错误对象

运行此代码将为您提供以下信息:

Output
Uncaught Error: Parameters are required. at namedSumFunction (<anonymous>:3:23) at <anonymous>:1:1

在这种情况下,命名函数可以让您快速了解错误所在。

一个箭头函数表达式与“胖箭头”语法编写一个匿名函数表达式(=>)。

sum用箭头函数语法重写函数:

const sum = (a, b) => {
  return a + b
}

像传统的函数表达式一样,箭头函数没有被提升,所以你不能在声明它们之前调用它们。它们也总是匿名的——无法命名箭头函数。在下一节中,您将探索更多箭头函数与传统函数之间的语法和实际差异。

箭头函数行为和语法

箭头函数在工作方式上有一些重要的区别,将它们与传统函数区分开来,以及一些语法增强。最大的功能差异是箭头函数没有自己的this绑定或原型,不能用作构造函数。箭头函数也可以编写为传统函数的更紧凑的替代方案,因为它们允许省略参数周围的括号并添加带有隐式返回的简洁函数体的概念。

在本节中,您将通过示例来说明每种情况。

词汇 this

关键字this在 JavaScript 中通常被认为是一个棘手的话题。文章了解这一点,绑定,呼叫,并在JavaScript应用说明了如何this工作,以及如何this可以隐含推断基于程序是否使用它在全球范围内,作为一个对象中的一个方法,作为构造一个函数或类,或作为DOM事件处理程序。

箭头函数有lexicalthis,这意味着 的值this由周围的范围(词法环境)决定。

下一个示例将演示传统函数和箭头函数处理方式的区别this在以下printNumbers对象中,有两个属性:phrasenumbers对象上还有一个方法loop,它应该在 中打印phrase字符串和当前值numbers

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop() {
    this.numbers.forEach(function (number) {
      console.log(this.phrase, number)
    })
  },
}

人们可能希望该loop函数在每次迭代的循环中打印字符串和当前数字。但是,在运行该函数的结果中,phrase实际上是undefined

printNumbers.loop()

这将给出以下内容:

Output
undefined 1 undefined 2 undefined 3 undefined 4

如上所示,this.phraseis undefined,说明this匿名函数内部传入的forEach方法没有引用printNumbers对象。这是因为传统的函数不会this从环境的范围来确定它的值,也就是printNumbers对象。

在旧版本的 JavaScript 中,您必须使用bind方法,该方法显式设置this. ES6 出现之前,这种模式经常可以在一些早期版本的框架中找到,比如React

使用bind修复功能:

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop() {
    // Bind the `this` from printNumbers to the inner forEach function
    this.numbers.forEach(
      function (number) {
        console.log(this.phrase, number)
      }.bind(this),
    )
  },
}

printNumbers.loop()

这将给出预期的结果:

Output
The current value is: 1 The current value is: 2 The current value is: 3 The current value is: 4

箭头函数提供了一种更直接的处理方式。由于它们的this值是基于词法范围确定的,因此调用的内部函数forEach现在可以访问外部printNumbers对象的属性,如下所示:

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop() {
    this.numbers.forEach((number) => {
      console.log(this.phrase, number)
    })
  },
}

printNumbers.loop()

这将给出预期的结果:

Output
The current value is: 1 The current value is: 2 The current value is: 3 The current value is: 4

这些示例表明,在内置数组方法(如forEachmapfilter和 )中使用箭头函数reduce可以更直观、更易于阅读,从而使该策略更有可能满足预期。

箭头函数作为对象方法

虽然箭头函数作为传递给数组方法的参数函数非常出色,但它们作为对象方法并不有效,因为它们对this. 使用与之前相同的示例,获取该loop方法并将其转换为箭头函数以发现它将如何执行:

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop: () => {
    this.numbers.forEach((number) => {
      console.log(this.phrase, number)
    })
  },
}

在这种情况下,一个对象方法,this应该是指对象的属性和方法printNumbers但是,由于对象不会创建新的词法作用域,因此箭头函数将在对象之外寻找 的值this

调用loop()方法:

printNumbers.loop()

这将给出以下内容:

Output
Uncaught TypeError: Cannot read property 'forEach' of undefined

由于对象不创建词法作用域,箭头函数方法this在外部作用域中查找——Window在本例中。由于对象numbers上不存在该Window属性,因此会引发错误。作为一般规则,默认情况下使用传统函数作为对象方法更安全。

箭头函数没有constructorprototype

本系列前面了解 JavaScript 中的原型和继承教程解释了函数和类有一个prototype属性,这是 JavaScript 用作克隆和继承的蓝图。

为了说明这一点,创建一个函数并记录自动分配的prototype属性:

function myFunction() {
  this.value = 5
}

// Log the prototype property of myFunction
console.log(myFunction.prototype)

这会将以下内容打印到控制台:

Output
{constructor: ƒ}

这表明在prototype属性中有一个带有constructor. 这允许您使用new关键字来创建函数的实例:

const instance = new myFunction()

console.log(instance.value)

这将产生value您第一次声明函数时定义属性的值

Output
5

相反,箭头函数没有prototype属性。创建一个新的箭头函数并尝试记录其原型:

const myArrowFunction = () => {}

// Attempt to log the prototype property of myArrowFunction
console.log(myArrowFunction.prototype)

这将给出以下内容:

Output
undefined

由于缺少prototype属性,new关键字不可用,您无法从箭头函数构造实例:

const arrowInstance = new myArrowFunction()

console.log(arrowInstance)

这将给出以下错误:

Output
Uncaught TypeError: myArrowFunction is not a constructor

这与我们之前的示例一致:由于箭头函数没有自己的this值,因此您将无法将箭头函数用作构造函数。

如这里所示,箭头函数有很多细微的变化,这使得它们的操作与 ES5 及更早版本中的传统函数不同。还有一些可选的语法更改,使编写箭头函数更快、更简洁。下一节将展示这些语法更改的示例。

隐式回报

传统函数的主体包含在使用大括号的块中,{}并在代码遇到return关键字时结束以下是这个实现作为箭头函数的样子:

const sum = (a, b) => {
  return a + b
}

箭头函数引入了简洁的正文语法,或隐式返回这允许省略大括号和return关键字。

const sum = (a, b) => a + b

隐式返回是用于创建简洁一个行操作有用mapfilter和其他常见阵列的方法。请注意,return必须省略括号和关键字。如果您不能将主体编写为单行 return 语句,那么您将不得不使用正常的块主体语法。

在返回对象的情况下,语法要求您将对象字面量括在括号中。否则,方括号将被视为函数体并且不会计算return值。

为了说明这一点,请找到以下示例:

const sum = (a, b) => ({result: a + b})

sum(1, 2)

这将提供以下输出:

Output
{result: 3}

省略单个参数周围的括号

另一个有用的语法增强是从函数中的单个参数周围删除括号的能力。在以下示例中,该square函数仅对一个参数进行操作x

const square = (x) => x * x

因此,您可以省略参数周围的括号,它的工作原理相同:

const square = x => x * x

square(10)

这将给出以下内容:

Output
100

请注意,如果函数不带参数,则需要括号:

const greet = () => 'Hello!'

greet()

调用greet()将按如下方式工作:

Output
'Hello!'

一些代码库选择尽可能省略括号,而其他代码库则选择始终在参数周围保留括号,尤其是在使用TypeScript并需要有关每个变量和参数的更多信息的代码库中在决定如何编写箭头函数时,请查看您参与的项目的风格指南。

结论

在本文中,您回顾了传统函数以及函数声明和函数表达式之间的区别。您了解到箭头函数始终是匿名的,没有prototypeor constructor,不能与new关键字一起使用,并this通过词法作用域确定 的值最后,您探索了可用于箭头函数的新语法增强,例如单参数函数的隐式返回和括号省略。

要查看基本函数,请阅读如何在 JavaScript 中定义函数要了解有关 JavaScript 中作用域和提升概念的更多信息,请阅读理解JavaScript 中的变量、作用域和提升

觉得文章有用?

点个广告表达一下你的爱意吧 !😁