“箭头函数”和“普通函数”是否相同/可互换?

811

ES2015中的箭头函数提供了更简洁的语法。

  • 我现在可以用箭头函数替换所有的函数声明/表达式吗?
  • 我需要注意什么?

示例:

构造函数

function User(name) {
  this.name = name;
}

// vs

const User = name => {
  this.name = name;
};

原型方法

User.prototype.getName = function() {
  return this.name;
};

// vs

User.prototype.getName = () => this.name;

对象(字面量)方法

const obj = {
  getName: function() {
    // ...
  }
};

// vs

const obj = {
  getName: () => {
    // ...
  }
};

回调函数

setTimeout(function() {
  // ...
}, 500);

// vs

setTimeout(() => {
  // ...
}, 500);

可变参数函数

function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// vs
const sum = (...args) => {
  // ...
};

13
随着ES2015越来越流行,有关箭头函数的类似问题也越来越多。我觉得没有一个好的标准问题/答案来解决这个问题,所以我创建了这个问题。如果您认为已经有一个很好的问题,请告诉我,我会将此问题关闭为重复或删除它。请随意改进示例或添加新示例。 - Felix Kling
2
JavaScript Ecma6将普通函数更改为箭头函数的问题怎么样?当然,一个常规问题永远不能像一个专门编写的标准问题那样好和通用。 - Bergi
看看这个 Plnkr 的例子:http://plnkr.co/edit/09Lh6D。变量 this 很不同,每次调用按钮时 timesCalled 只增加 1。这回答了我的个人问题:.click( () => { } ).click(function() { }) 在循环中使用时都创建相同数量的函数,正如你可以从 Plnkr 中的 Guid 计数中看到的那样。 - jmbmage
相关:“this”关键字如何工作? - Sebastian Simon
4个回答

1089

tl;dr: 不行! 箭头函数和函数声明/表达式不等价,不能盲目替换。
如果要替换的函数使用thisarguments,并且没有使用new调用,则可以。


常规而言:视情况而定。箭头函数与函数声明/表达式具有不同的行为,因此让我们先看一下它们之间的区别:

1. 词法作用域中的thisarguments

箭头函数没有自己的thisarguments绑定。相反,这些标识符在词法作用域中像任何其他变量一样进行解析。这意味着在箭头函数内部,thisarguments指的是环境中的thisarguments的值,即箭头函数所在的环境(即"外部"箭头函数)中的值:

// Example using a function expression
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: function() {
      console.log('Inside `bar`:', this.foo);
    },
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

// Example using a arrow function
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: () => console.log('Inside `bar`:', this.foo),
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

在函数表达式的情况下,this指的是在createObject内部创建的对象。而在箭头函数中,this指的是createObject本身的this
这使得箭头函数在需要访问当前环境的this时非常有用。
// currently common pattern
var that = this;
getData(function(data) {
  that.data = data;
});

// better alternative with arrow functions
getData(data => {
  this.data = data;
});

注意,这也意味着无法使用 .bind.call 来设置箭头函数的 this

如果你对 this 不是很熟悉,建议阅读:

2. 箭头函数不能通过 new 调用

ES2015 区分了可调用和可构造函数。如果一个函数可以被构造,它可以通过 new 调用,例如 new User()。如果一个函数可以被调用,它可以在没有 new 的情况下被调用(即正常函数调用)。

函数声明/表达式创建的函数既可以构造也可以调用。
箭头函数(和方法)只能被调用。 class 构造函数只能被构造。

如果您试图调用一个不可调用的函数或构造一个不可构造的函数,将会出现运行时错误。


了解这一点后,我们可以得出以下结论:

可以替换的:

  • 不使用 thisarguments 的函数。
  • .bind(this) 一起使用的函数

无法 替换的:

  • 构造函数
  • 添加到原型中的函数/方法(因为它们通常使用 this
  • 变参函数(如果它们使用 arguments(见下文))
  • 生成器函数,需要使用 function* 表示法

让我们使用您提供的示例更详细地看看这些内容:

构造函数

这不起作用,因为箭头函数不能通过 new 调用。继续使用函数声明/表达式或使用 class

原型方法

很可能不行,因为原型方法通常使用 this 访问实例。如果它们不使用 this,则可以替换它们。但是,如果您主要关心简洁的语法,请使用 class 和其简洁的方法语法:

class User {
  constructor(name) {
    this.name = name;
  }
  
  getName() {
    return this.name;
  }
}

对象方法

对于对象字面量中的方法同样适用。如果方法想要通过 this 引用对象本身,请继续使用函数表达式或使用新的方法语法:

const obj = {
  getName() {
    // ...
  },
};

回调函数

这取决于情况。如果你正在别名化外部的this,或者正在使用.bind(this),那么你应该肯定要替换它:

// old
setTimeout(function() {
  // ...
}.bind(this), 500);

// new
setTimeout(() => {
  // ...
}, 500);

但是: 如果调用回调函数的代码显式地将this设置为特定值,这在事件处理程序中经常发生,特别是在jQuery中,而回调函数使用this(或arguments),则不能使用箭头函数!

可变参数函数

由于箭头函数没有自己的arguments,因此您不能简单地将它们替换为箭头函数。但是,ES2015引入了一种替代方案来使用arguments: rest参数

// old
function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// new
const sum = (...args) => {
  // ...
};

相关问题:

更多资源:


16
值得一提的是,词法上的 this 也会影响到 super,它们没有 .prototype - loganfsmyth
1
另外需要提到的是,它们在语法上并不可互换 - 箭头函数(AssignmentExpression)不能随处放置在函数表达式(PrimaryExpression)可以放置的任何地方,这经常会让人们感到困惑(特别是因为主要的 JS 实现中存在解析错误)。 - JMM
@FelixKling 对重要概念有很好的掌握。不过我刚注意到你错过了函数表达式和函数声明之间的区别。函数声明语句始终以 function 关键字开头,而函数表达式则以除 function 关键字以外的任何内容开头。这就是使 IIFEs 成为函数表达式而不是函数声明的原因之一,IIFEs 是将函数分配给变量的函数表达式之一。虽然如此,你对所有这些进行了很好的解释。赞! - candy_man
@Paul:谢谢!有其他关于函数声明与表达式的更详细的问答。这个问答假设读者已经知道它们之间的区别。虽然我理解你的意思,但并不完全正确:函数表达式也总是以 function 关键字开头,但在 function 关键字之前的内容决定了函数定义是解释为声明还是表达式。那部分内容并不属于函数表达式。例如,如果你有 foo = function (){},那么 = 是赋值表达式的一部分,而不是函数表达式的一部分。 - Felix Kling
@FelixKling 在 https://astexplorer.net/ 上的函数声明图像:https://ibb.co/dKjY9b 与函数表达式图像:https://ibb.co/itp0pb。 - candy_man
显示剩余15条评论

49

箭头函数 => 目前为止最好的ES6功能。它们是ES6的一个非常强大的补充,我经常使用它们。

然而,在代码中并不是所有情况都适用箭头函数,比如this就不能使用箭头函数。毫无疑问,箭头函数是一个很棒的补充,它带来了代码简洁性。

但当需要动态上下文时,不能使用箭头函数:定义方法、创建带有构造函数的对象、在处理事件时获取目标。

不应该使用箭头函数的原因:

  1. 它们没有this

    它使用“词法作用域”来确定“this”的值应该是什么。简单来说,词法作用域从函数体内部使用“this”。

  2. 它们没有arguments

    箭头函数没有arguments对象。但可以使用rest参数实现相同的功能。

let sum = (...args) => args.reduce((x, y) => x + y, 0);
sum(3, 3, 1) // output: 7
  1. 它们不能与 new 一起使用

    箭头函数不能作为构造函数,因为它们没有原型属性。

何时使用箭头函数,何时不用:

  1. 不要用于将函数添加为对象文字中的属性,因为我们无法访问此关键字。
  2. 函数表达式最适合用于对象方法。箭头函数最适合用于回调或类似 mapreduceforEach 的方法。
  3. 使用函数声明来命名调用的函数(因为它们被提前了)。
  4. 使用箭头函数进行回调(因为它们往往更简洁)。

7
抱歉,"它们没有参数"这句话不太准确,可以在不使用...运算符的情况下拥有参数。也许您想说的是它们没有数组作为参数。 - Carmine Tambascia
7
请查阅有关特殊的arguments对象的信息,该对象在箭头函数中不可用。这里是链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments - vichle

2

它们并不总是等价的。有一种情况,您在其中不能简单地使用箭头函数代替常规函数。

箭头函数不能用作构造函数

TLDR:

这是因为箭头函数如何使用this关键字。如果JS看到箭头函数被调用为“构造函数”,它将简单地抛出一个错误。请使用常规函数来修复此错误。

更长的解释:

这是因为对象“构造函数”依赖于this关键字才能进行修改。

通常,this关键字始终引用全局对象。(在浏览器中是window对象)。

但是,当您执行以下操作时:

function personCreator(name) {
   this.name = name;
}

const person1 = new personCreator('John');

新的关键字发挥了其神奇作用,并使得位于personCreator内部的this关键字最初成为一个空对象,而不是引用全局对象。之后,在该空白的this对象内创建了一个名为name的新属性,其值将为'John'。最后,返回该this对象。
我们看到,new关键字改变了this的值,使其不再引用全局对象,而是成为空对象{}。
箭头函数不允许修改其this对象。它们的this对象始终是静态创建它们的范围中的this对象。这称为静态词法作用域。这就是为什么您不能对箭头函数执行像bind、apply或call这样的操作。简单地说,它们的this被锁定为它们被创建的范围中的this值。这是按设计来实现的。
正因为如此:D,箭头函数无法用作“构造函数”。
附注:
词法作用域只是函数创建的区域。例如:
function personCreator(name) {
    this.name = name;

    const foo = () => {
        const bar = () => {
            console.log(this); // Output: { name: 'John' }
        }

        console.log(this); // Output: { name: 'John' }
    
        bar();
    }

    foo();
}

const person1 = new personCreator('John');
< p > bar 的词法作用域是 foo 内的所有内容。因此,barthis 值是 foo 的值,即 personCreator 的值。


1
这个箭头函数不可构造是被接受的答案中的第二点,该答案还解释了它们如何处理this。非构造性和this的值也在这个答案中提到。 - VLAZ
我很欣赏这个回答。它花时间解释了this作用域的工作原理,这是我之前不理解的。这意味着对我来说,被接受的答案就像一堵墙一样,直到这个更简洁的答案帮助我理解。然后我能够回到被接受的答案并吸收它。 - 8bitjunkie

2
为了在使用function.prototype.call时使用箭头函数,我在对象原型上创建了一个辅助函数:
// Using
// @func = function() {use this here} or This => {use This here}
using(func) {
  return func.call(this, this);
}

使用方法

var obj = {f:3, a:2}
.using(This => This.f + This.a) // 5

你不需要一个帮手。你可以自己做:
var obj = {f:3, a:2}
(This => This.f + This.a).call(undefined, obj); // 5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接