一文讲透ES6箭头函数

12/25/2019 JavaScript

# 前言

箭头函数是 ES6 引入到 JavaScript 中的,是一种新式的匿名函数的写法,类似于其他语言中 Lambda 函数。箭头函数和传统函数有很多的不同,例如作用域、语法写法等等。

# 函数定义

在深入介绍箭头函数和传统函数异同之前,我们先了解一下传统函数的使用,这样将更好的理解箭头函数的异同。

下面是一个 sum 函数的定义,可以返回两个参数之和。

function sum(a, b) {
  return a + b;
}
1
2
3

对于传统函数,你甚至可以在定义之前调用该函数

sum(1, 2);

function sum(a, b) {
  return a + b;
}
1
2
3
4
5

你可以通过函数名,打印出其函数申明

console.log(sum);
1

输出结果如下:

ƒ sum(a, b) {
  return a + b
}
1
2
3

函数表达式通常可以有个名字,但是也可以是匿名的,意味着函数是没有名字的。

下面例子是 sum 函数的匿名申明方式:

const sum = function(a, b) {
  return a + b;
};
1
2
3

这时我们将匿名函数赋值给了 sum 变量,如果这时候在定义之前调用会导致错误:

sum(1, 2);

const sum = function(a, b) {
  return a + b;
};
1
2
3
4
5

错误返回:Uncaught ReferenceError: Cannot access 'sum' before initialization

我们也可以把 sum 打印出来:

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

console.log(sum);
1
2
3
4
5

打印出来的结果如下:

ƒ (a, b) {
  return a + b
}
1
2
3

sum 是一个匿名函数,而不是一个有名字的函数。

# 箭头函数

箭头函数和普通函数有非常多的不同,其中最大的不同是箭头函数没有没有自己的 this 绑定,没有 prototype,同时也不能被用做构造函数。同时,箭头函数可以作为普通函数提供更为简洁的写法。

# this 绑定

在 JavaScript 中,this 往往是一个比较复杂诡异的事情。在 JavaScript 中有 bind、apply、call 等方法会影响 this 所指定的对象。

箭头函数的 this 是语义层面的,因此,箭头函数中的 this 是由上下文作用域决定的。下面的例子会解释 this 在普通函数和箭头函数的区别:

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

  loop() {
    this.numbers.forEach(function(number) {
      console.log(this.phrase, number);
    });
  },
};
1
2
3
4
5
6
7
8
9
10

你也许会期望 loop 函数会打印出文本和对应的数字,然后真正返回的内容其实是 undefined:

printNumbers.loop();
1

输出:

undefined 1
undefined 2
undefined 3
undefined 4
1
2
3
4

在上面的例子中,this.phrase 代表了 undefined,说明在 forEach 方法中的匿名函数中的 this 并不会指向 printNumber 对象。这是因为普通函数并不是通过上下文作用域来决定 this 的值,而是通过实际调用函数的对象来决定的。

在老版本的 JavaScript,你可以通过 bind 方法来显示的绑定 this。使用 bind 的方式如下:

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

  loop() {
    // 将外部的printNumber对象绑定到内部forEach函数中的'this'
    this.numbers.forEach(
      function(number) {
        console.log(this.phrase, number);
      }.bind(this)
    );
  },
};

printNumbers.loop();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这将输出正确的结果:

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

箭头函数提供了一个更为优雅的解决方案,由于其 this 的指向是由上下文作用域来决定的,因此它会指向 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();
1
2
3
4
5
6
7
8
9
10
11
12

# 箭头函数作为对象方法

箭头函数在循环中可以很好的使用,但是作为对象方法却会造成问题。

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

  loop: () => {
    this.numbers.forEach((number) => {
      console.log(this.phrase, number);
    });
  },
};
1
2
3
4
5
6
7
8
9
10

调用 loop 方法

printNumbers.loop();
1

结果会出现如下错误:

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

这是因为在 loop 箭头函数申明的时候,this 指向并不会是 printNumbers 对象,而是外部的 Window。而 Window 上面并没有 numbers 字段,从而导致错误。因此,对象方法一般使用传统方法。

# 箭头函数没有 Prototype 和构造函数

在 JavaScript 中的 Function 或者 Class 中都会有一个 Prototype 属性,这个可以用于对象拷贝或者继承。

function myFunction() {
  this.value = 5;
}

// Log the prototype property of myFunction
console.log(myFunction.prototype);
1
2
3
4
5
6

输出:

{
  constructor: ƒ;
}
1
2
3

但是,箭头函数是不存在 Prototype 属性,我们可以尝试打印出箭头函数的 Prototype。

const myArrowFunction = () => {};

// Attempt to log the prototype property of myArrowFunction
console.log(myArrowFunction.prototype);
1
2
3
4

输出:

undefined;
1

同时,由于没有 Prototype 属性,因此也没法通过 new 进行实例化。

const arrowInstance = new myArrowFunction();
console.log(arrowInstance);
1
2

输出错误:

Uncaught TypeError: myArrowFunction is not a constructor
1

箭头函数除了这些变化之外,还有些语法上的不同,可以让写法变得更简洁。

# 隐含返回

传统的方法都是需要大括号{},并且通过 return 语句返回值。

const sum = (a, b) => {
  return a + b;
};
1
2
3

箭头函数可以简化函数声明,并且提供隐含返回值。

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

# 单个参数省略括号

例如:

const square = (x) => x * x;
1

当只有一个参数的时候,可以省略(),写成如下形式:

const square = (x) => x * x;
square(10);
1
2

输出:100

有些代码库总是尝试忽略(),而有的代码库总是保留,尤其是采用 TypeScript 的代码库,需要声明每个参数的类型。因此,你需要首先规定好代码库的风格,并且始终保持。

# 写在最后

在这篇文章中,你了解了箭头函数和传统函数的区别。箭头函数始终是匿名的,没有 Prototype 或者构造函数,无法用 new 进行实例化,并且是通过语义上下文来决定 this 指向。然后,你又了解了关于箭头函数的语法优化,例如隐含返回、单个参数省略括号等。