箭头函数 vs 常规函数

JavaScript 的箭头函数从表面上看可能与常规函数相同,但它们有一些非常重要的区别:

关键区别

  • 语法
  • this 值(执行上下文)
  • 用作类方法
  • 用作构造函数
  • arguments 参数绑定

#. 语法

1
2
3
4
5
6
7

const square = a => a * a; // 允许省略单个参数周围的括号; 隐式返回;

// 等效常规function
function square(a) {
  return a * a;
}

#. 执行上下文

  • this
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

const logThisArrow = () => {
  console.log(this);
};
document.addEventListener('click', logThisArrow);
// `this` 指的是全局对象

// 常规函数
function logThis() {
  console.log(this);
}
document.addEventListener('click', logThis);
// `this` 指的是 document
  • Function.prototype.call() Function.prototype.bind() Function.prototype.apply()

以上3个方法箭头函数也不能工作。因为这三个方法目的式允许函数在不同的范围内执行。
但是箭头函数的 this 值不能改变,因为它是词法解析的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

const logThisArrow = () => {
  console.log(this);
};
logThisArrow.call(42);  // Logs: 全局对象

// 常规函数
function logThis() {
  console.log(this);
}
logThis.call(42);       // Logs: 42

#. 用作类方法

由于箭头函数没有定义自己的执行上下文,因此它们不太适合用作方法。
然而,由于Class fields proposal,如果您的环境支持,箭头函数可以用作类内部的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

const obj = {
  x: 42,
  logThisX: function() {
    console.log(this.x, this);
  },
  logThisXArrow: () => {
    console.log(this.x, this);
  }
};

obj.logThisX();       // Logs: 42, Object {...}
obj.logThisXArrow();  // Logs: undefined, the global object

#. 用作构造函数

常规函数可以用作构造函数,使用new关键字。

this内部箭头函数的词法解析的另一个结果是它们不能用作构造函数。使用new在一个箭头作用的结果TypeError

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

const Bar = foo => {
  this.foo = foo;
};
const b = new Bar(42);  // TypeError: Bar is not a constructor

function Foo(bar) {
  this.bar = bar;
}
const a = new Foo(42);  // Foo {bar: 42}

#. arguments 参数绑定

另一个区别是arguments对象的绑定。
与常规函数不同,箭头函数没有自己的arguments对象。绕过此限制的现代替代方案是使用其余参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

function sum() {
  return arguments[0] + arguments[1];
};
sum(4, 6);        // 10

const arguments = [1, 2, 3];
const sumArrow = () => {
  return arguments[0] + arguments[1];
};
sumArrow(4, 6);   // 3 (resolves to 1 + 2)

const sumRest = (...arguments) => {
  return arguments[0] + arguments[1];
}
sumRest(4, 6);    // 10

#. 其他差异

最后,还有一些其他差异并不那么重要,但值得一提。
这些包括缺少prototype箭头函数中的属性,以及yield关键字可能不会在箭头函数的主体中使用的事实。
后者的结果是箭头函数不能用作生成器。