465

我们得到了两种非常相似的方式来在 ES6 中编写函数() => {}function () {}在其他语言中,lambda 函数通常通过匿名来区分自己,但在 ECMAScript 中,任何函数都可以是匿名的。这两种类型中的每一种都有唯一的使用域(即this需要显式绑定或显式不绑定时)。在这些域之间,有大量的情况可以使用任何一种表示法。

ES6 中的箭头函数至少有两个限制:

  • 创建时不要使用new,也不能使用prototype
  • this在初始化时固定绑定到范围

除了这两个限制之外,箭头函数理论上几乎可以在任何地方取代常规函数。在实践中使用它们的正确方法是什么?是否应该使用箭头函数,例如:

  • “它们工作的所有地方”,即在任何地方,函数都不必对this变量不可知,而且我们不是在创建对象。
  • 只有“需要它们的任何地方”,即需要绑定到某个范围的事件监听器、超时
  • 具有“短”功能,但不具有“长”功能
  • 仅适用于不包含另一个箭头函数的函数

我正在寻找在未来版本的 ECMAScript 中选择适当函数表示法的指南。该指南需要清晰,以便可以教授给团队中的开发人员,并且要保持一致,这样就不需要不断地从一个函数符号到另一个函数符号来回重构。

这个问题是针对那些在即将到来的 ECMAScript 6 (Harmony) 的上下文中考虑过代码风格并且已经使用过该语言的人。

4

9 回答 9

351

不久前,我们的团队将其所有代码(一个中型 AngularJS 应用程序)迁移到使用Traceur Babel编译的 JavaScript 。我现在对 ES6 及更高版本中的函数使用以下经验法则:

  • function在全局范围和Object.prototype属性中使用。
  • 用于class对象构造函数。
  • =>在其他任何地方使用。

为什么几乎到处都使用箭头函数?

  1. 范围安全:当箭头函数被一致使用时,一切都保证与thisObject根相同。如果即使是单个标准函数回调与一堆箭头函数混合在一起,范围也可能会变得混乱。
  2. 紧凑性:箭头函数更容易读写。(这可能看起来很自以为是,所以我将进一步举几个例子。)
  3. 清晰性:当几乎所有东西都是箭头函数时,任何规则都会function立即伸出来定义范围。开发人员可以随时查看下一个更高的function语句以了解它thisObject是什么。

为什么总是在全局范围或模块范围内使用常规函数?

  1. 指示不应访问的函数thisObject
  2. 对象(window全局范围)最好显式处理。
  3. 许多Object.prototype定义存在于全局范围内(thinkString.prototype.truncate等),而且这些定义通常都必须是类型function的。在全局范围内一致使用function有助于避免错误。
  4. 全局范围内的许多函数都是旧式类定义的对象构造函数。
  5. 函数可以命名为1。这有两个好处:(1) 写起来function foo(){}比其他函数调用更不尴尬const foo = () => {}——尤其是在其他函数调用之外。(2) 函数名显示在堆栈跟踪中。虽然命名每个内部回调会很乏味,但命名所有公共函数可能是一个好主意。
  6. 函数声明被提升,(意味着它们可以在声明之前被访问),这是静态实用函数中的一个有用属性。

对象构造函数

尝试实例化箭头函数会引发异常:

var x = () => {};
new x(); // TypeError: x is not a constructor

因此,函数相对于箭头函数的一个关键优势是函数可以兼作对象构造函数:

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

但是,功能相同的2 ECMAScript Harmony草案类定义几乎一样紧凑:

class Person {
    constructor(name) {
        this.name = name;
    }
}

我预计最终将不鼓励使用前一种表示法。对象构造函数符号可能仍被一些人用于简单的匿名对象工厂,其中对象是通过编程生成的,但在其他方面则不多。

在需要对象构造函数的地方,应考虑将函数转换为 a class,如上所示。该语法也适用于匿名函数/类。

箭头函数的可读性

坚持常规函数的最佳论据——该死的范围安全——可能是箭头函数的可读性不如常规函数。如果你的代码一开始就没有功能,那么箭头函数可能看起来就没有必要了,而且当箭头函数没有被一致使用时,它们看起来很丑。

自从 ECMAScript 5.1 为我们提供了函数式和所有这些函数式编程特性以来,ECMAScript 发生了相当大的变化Array.forEachArray.map这些特性使我们可以使用以前使用for循环的函数。异步 JavaScript 已经取得了相当大的进展。ES6 还将发布一个Promise对象,这意味着更多的匿名函数。函数式编程没有回头路。在函数式 JavaScript 中,箭头函数优于常规函数。

以这段(特别令人困惑的)代码3为例:

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(articles => Promise.all(articles.map(article => article.comments.getList())))
        .then(commentLists => commentLists.reduce((a, b) => a.concat(b)));
        .then(comments => {
            this.comments = comments;
        })
}

具有常规功能的同一段代码:

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(function (articles) {
            return Promise.all(articles.map(function (article) {
                return article.comments.getList();
            }));
        })
        .then(function (commentLists) {
            return commentLists.reduce(function (a, b) {
                return a.concat(b);
            });
        })
        .then(function (comments) {
            this.comments = comments;
        }.bind(this));
}

虽然任何一个箭头函数都可以被标准函数替换,但这样做几乎没有什么好处。哪个版本更易读?我会说第一个。

我认为随着时间的推移,使用箭头函数还是常规函数的问题将变得不那么重要。大多数函数要么变成类方法,从而去掉function关键字,要么变成类。函数将继续用于通过Object.prototype. 同时,我建议function为任何真正应该是类方法或类的东西保留关键字。


笔记

  1. 命名箭头函数在 ES6 规范中被推迟了。他们可能仍会被添加到未来的版本中。
  2. 根据规范草案,“类声明/表达式创建构造函数/原型对,就像函数声明一样”,只要类不使用extend关键字。一个小的区别是类声明是常量,而函数声明不是。
  3. 关于单语句箭头函数中的块的注意事项:我喜欢在单独调用箭头函数的地方使用块(例如,赋值)。这样一来,很明显可以丢弃返回值。
于 2014-04-13T16:25:04.533 回答
87

根据提案,箭头旨在“解决和解决传统函数表达式的几个常见痛点”。他们打算通过this词法绑定和提供简洁的语法来改善问题。

然而,

  • 不能始终如一地在this词汇上绑定
  • 箭头函数语法微妙且模棱两可

因此,箭头函数会产生混淆和错误的机会,应该从 JavaScript 程序员的词汇表中排除,替换为function独占。

关于词汇this

this有问题:

function Book(settings) {
    this.settings = settings;
    this.pages = this.createPages();
}
Book.prototype.render = function () {
    this.pages.forEach(function (page) {
        page.draw(this.settings);
    }, this);
};

箭头函数旨在解决我们需要访问this回调内部属性的问题。已经有几种方法可以做到这一点:一种可以分配this给变量,使用,或使用聚合方法bind上可用的第三个参数。Array然而,箭头似乎是最简单的解决方法,因此可以像这样重构该方法:

this.pages.forEach(page => page.draw(this.settings));

但是,考虑一下代码是否使用了像 jQuery 这样的库,它的方法是this专门绑定的。现在,有两个this值需要处理:

Book.prototype.render = function () {
    var book = this;
    this.$pages.each(function (index) {
        var $page = $(this);
        book.draw(book.currentPage + index, $page);
    });
};

我们必须使用function为了动态each绑定this。我们不能在这里使用箭头函数。

处理多个this值也可能令人困惑,因为很难知道this作者在谈论哪个:

function Reader() {
    this.book.on('change', function () {
        this.reformat();
    });
}

作者真的打算打电话Book.prototype.reformat吗?还是他忘记绑定了this,打算打电话Reader.prototype.reformat?如果我们将处理程序更改为箭头函数,我们同样会怀疑作者是否想要动态this,但选择了一个箭头,因为它适合一行:

function Reader() {
    this.book.on('change', () => this.reformat());
}

有人可能会提出:“箭头有时可能是错误的函数,这很奇怪吗?也许如果我们只是很少需要动态this值,那么大多数时候使用箭头仍然是可以的。”

但是问问自己这个问题:“调试代码并发现错误的结果是由‘极端情况’引起的‘值得’吗?”我宁愿避免麻烦,不仅仅是大多数时候,而是100% 的时间。

有一个更好的方法:始终使用function(因此this始终可以动态绑定),并且始终this通过变量进行引用。变量是词法的并采用许多名称。分配this给变量将使您的意图明确:

function Reader() {
    var reader = this;
    reader.book.on('change', function () {
        var book = this;
        book.reformat();
        reader.reformat();
    });
}

此外,始终分配this给一个变量(即使只有一个this或没有其他函数)确保即使在代码更改后,一个人的意图仍然清晰。

此外,动态this几乎不例外。jQuery 被用于超过 5000 万个网站(截至 2016 年 2 月撰写本文时)。以下是其他动态绑定的 API this

  • Mocha(昨天下载量约为 120k)通过this.
  • Grunt(昨天下载量约为 63k)通过this.
  • Backbone(昨天下载了约 22k 次)定义了访问this.
  • 事件 API(如 DOM)指的是EventTargetwith this
  • 修补或扩展的原型API 指的是带有this.

(通过http://trends.builtwith.com/javascript/jQuery和 https://www.npmjs.com 统计。)

您可能已经需要动态this绑定。

有时会出现词汇this,但有时不会;就像this有时期望的动态一样,但有时却不是。值得庆幸的是,有一种更好的方法,它总是产生和传达预期的绑定。

关于简洁的语法

箭头函数成功地为函数提供了一种“更短的句法形式”。但是这些更短的功能会让你更成功吗?

x => x * x更容易阅读”比function (x) { return x * x; }?也许是这样,因为它更有可能产生一个单一的、短的代码行。根据戴森的《阅读速度和行长对屏幕阅读效果的影响》

中等长度的行(每行 55 个字符)似乎支持以正常和快速的速度进行有效阅读。这产生了最高层次的悟性。. .

条件(三元)运算符和单行if语句也有类似的理由。

但是,您真的在编写提案中宣传的简单数学函数吗?我的领域不是数学的,所以我的子程序很少如此优雅。相反,我经常看到箭头函数打破了列限制,并且由于编辑器或样式指南而换行到另一行,这使 Dyson 定义的“可读性”无效。

有人可能会提出,“如果可能的话,只使用简短版本的简短功能怎么样?”。但是现在一个风格规则与语言约束相矛盾:“尽量使用最短的函数符号,记住有时只有最长的符号才能this按预期绑定。” 这种合并使得箭头特别容易被误用。

箭头函数语法有很多问题:

const a = x =>
    doSomething(x);

const b = x =>
    doSomething(x);
    doSomethingElse(x);

这两个函数在语法上都是有效的。却不doSomethingElse(x);在体内b。这只是一个缩进不佳的顶级语句。

当扩展到块形式时,不再有隐含return的 ,人们可能会忘记恢复它。但是这个表达式可能只是为了产生副作用,所以谁知道未来是否return需要显式?

const create = () => User.create();

const create = () => {
    let user;
    User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

const create = () => {
    let user;
    return User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

可能打算作为休息参数的内容可以解析为扩展运算符:

processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest

赋值可能与默认参数混淆:

const a = 1;
let x;
const b = x => {}; // No default
const b = x = a => {}; // "Adding a default" instead creates a double assignment
const b = (x = a) => {}; // Remember to add parentheses

块看起来像对象:

(id) => id // Returns `id`
(id) => {name: id} // Returns `undefined` (it's a labeled statement)
(id) => ({name: id}) // Returns an object

这是什么意思?

() => {}

作者是打算创建一个无操作,还是一个返回空对象的函数?(考虑到这一点,我们是否应该放置{在 之后=>?我们是否应该仅限于表达式语法?这将进一步降低箭头的频率。)

=>看起来像<=>=

x => 1 ? 2 : 3
x <= 1 ? 2 : 3

if (x => 1) {}
if (x >= 1) {}

要立即调用箭头函数表达式,必须放在()外面,但放在()里面是有效的并且可能是有意的。

(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function

虽然,如果一个人(() => doSomething()());的意图是编写一个立即调用的函数表达式,那么什么都不会发生。

考虑到上述所有情况,很难说箭头函数“更容易理解”。人们可以学习使用这种语法所需的所有特殊规则。是不是真的值得吗?

的语法function非常通用。独占意味着语言本身可以function防止编写令人困惑的代码。为了编写在所有情况下都应该在语法上理解的过程,我选择function.

关于指导方针

您要求的指南需要“清晰”和“一致”。使用箭头函数最终将导致语法有效、逻辑无效的代码,两种函数形式相互交织,有意义且任意。因此,我提供以下内容:

ES6 中的函数表示法指南:

  • 始终使用function.
  • 始终分配this给变量。不要使用() => {}.
于 2015-01-25T09:50:58.763 回答
60
于 2016-10-15T17:26:02.450 回答
20

箭头函数 - 迄今为止使用最广泛的 ES6 特性......

用法:所有 ES5 函数都应替换为 ES6 箭头函数,以下情况除外:

不应 使用箭头函数

  1. 当我们想要函数提升时
    • 因为箭头函数是匿名的。
  2. 当我们想在函数 中使用this/arguments
    • 由于箭头函数没有自己的this/ arguments,它们依赖于它们的外部上下文。
  3. 当我们想使用命名函数时
    • 因为箭头函数是匿名的。
  4. 当我们想将函数用作constructor
    • 因为箭头函数没有自己的this.
  5. 当我们想在对象字面量中添加函数作为属性并在其中使用对象时
    • 因为我们无法访问this(应该是对象本身)。

让我们了解一些箭头函数的变体以便更好地理解:

变体 1:当我们想将多个参数传递给函数并从中返回一些值时。

ES5 版本

var multiply = function (a, b) {
    return a*b;
};
console.log(multiply(5, 6)); // 30

ES6 版本

var multiplyArrow = (a, b) => a*b;
console.log(multiplyArrow(5, 6)); // 30

笔记:

function关键字不是必需 的。=>是必须的。 {}是可选的,当我们不提供{} return时由 JavaScript 隐式添加,当我们提供时,如果需要,{}我们需要添加return

变体 2:当我们只想将一个参数传递给函数并从中返回一些值时。

ES5 版本

var double = function(a) {
    return a*2;
};
console.log(double(2)); // 4

ES6 版本

var doubleArrow  = a => a*2;
console.log(doubleArrow(2)); // 4

笔记:

当只传递一个参数时,我们可以省略括号,().

变体 3:当我们不想将任何参数传递给函数并且不想返回任何值时。

ES5 版本

var sayHello = function() {
    console.log("Hello");
};
sayHello(); // Hello

ES6 版本

var sayHelloArrow = () => {console.log("sayHelloArrow");}
sayHelloArrow(); // sayHelloArrow

变体 4:当我们想从箭头函数显式返回时。

ES6 版本

var increment = x => {
  return x + 1;
};
console.log(increment(1)); // 2

变体 5:当我们想从箭头函数返回一个对象时。

ES6 版本

var returnObject = () => ({a:5});
console.log(returnObject());

笔记:

我们需要将对象包裹在括号中,(). 否则,JavaScript 无法区分块和对象。

变体 6:箭头函数没有自己arguments的(类似对象的数组)。它们依赖于外部上下文arguments

ES6 版本

function foo() {
  var abc = i => arguments[0];
  console.log(abc(1));
};
foo(2); // 2

笔记:

foo是一个 ES5 函数,具有arguments类似对象的数组和传递给它的参数,2因此arguments[0]foo2。

abc是一个 ES6 箭头函数,因为它没有自己arguments. 因此,它改为打印arguments[0]foo外部上下文。

变体 7:箭头函数没有自己this的,它们依赖于外部上下文this

ES5 版本

var obj5 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
        setTimeout(function(){
        console.log(this.greet + ": " +  user); // "this" here is undefined.
        });
     }
};

obj5.greetUser("Katty"); //undefined: Katty

笔记:

传递给 setTimeout 的回调是一个 ES5 函数,它有自己的this,在环境中是未定义的use-strict。因此我们得到输出:

undefined: Katty

ES6 版本

var obj6 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
    setTimeout(() => console.log(this.greet + ": " +  user));
      // This here refers to outer context
   }
};

obj6.greetUser("Katty"); // Hi, Welcome: Katty

笔记:

传递给的回调setTimeout是一个 ES6 箭头函数,它没有自己this,因此它从其外部上下文中获取它,即greetUserwhich has this。也就是说obj6,因此我们得到输出:

Hi, Welcome: Katty

各种各样的:

  • 我们不能使用new箭头函数。
  • 箭头函数没有属性prototype
  • 我们没有绑定this何时通过applyor调用箭头函数call
于 2017-09-24T13:08:08.507 回答
8

我仍然坚持我在这个帖子的第一个答案中写的所有内容。但是,从那时起,我对代码风格的看法有所发展,所以我对这个问题有了一个新的答案,这个答案建立在我上一个问题的基础上。

关于词汇this

在我的上一个回答中,我故意避开了我对这种语言的基本信念,因为它与我提出的论点没有直接关系。尽管如此,在没有明确说明的情况下,我可以理解为什么许多人在发现箭头非常有用时,会简单地拒绝我不使用箭头的建议。

我的信念是:我们不应该首先使用this。因此,如果一个人在他的代码中刻意避免使用this,那么箭头的“词汇this”特征就没有什么价值了。this还有,在是坏事的前提下,箭的处理this也不是什么“好事”;相反,它更像是对另一个糟糕的语言功能的一种损害控制形式。

我认为这要么不会发生在某些人身上,但即使对那些发生过的人来说,他们也必须总是发现自己在this每个文件出现一百次的代码库中工作,并且一点(或很多)损害控制就是全部一个有理智的人可以希望。因此,在某种意义上,箭可以使糟糕的情况变得更好。

即使使用箭头编写代码this比不使用箭头更容易,使用箭头的规则仍然非常复杂(参见:当前线程)。因此,如您所要求的,指导方针既不“清晰”也不“一致”。即使程序员知道箭头的歧义,我认为他们无论如何都会耸耸肩并接受它们,因为词汇的价值this超过了它们。

所有这一切都是以下认识的序言:如果不使用this,那么关于this箭头通常导致的歧义变得无关紧要。在这种情况下,箭头变得更加中性。

关于简洁的语法

当我写第一个答案时,我认为即使是一味地坚持最佳实践也是值得付出的代价,如果这意味着我可以编写更完美的代码。但我最终意识到,简洁可以作为一种抽象形式,也可以提高代码质量——足以证明有时偏离最佳实践是合理的。

换句话说:该死,我也想要单线函数!

关于指导方针

this考虑到-neutral 箭头函数的可能性,以及值得追求的简洁性,我提供以下更宽松的指导方针:

ES6 中的函数表示法指南:

  • 不要使用this.
  • 对按名称调用的函数使用函数声明(因为它们已被提升)。
  • 使用箭头函数进行回调(因为它们往往更简洁)。
于 2019-06-06T06:19:46.160 回答
7

除了到目前为止的出色答案之外,我还想提出一个非常不同的原因,为什么箭头函数在某种意义上从根本上优于“普通”JavaScript 函数。

为了便于讨论,让我们暂时假设我们使用TypeScript或 Facebook 的“Flow”之类的类型检查器。考虑以下玩具模块,它是有效的 ECMAScript 6 代码加上 Flow 类型注释(我将在此答案的末尾包含实际上由 Babel 产生的无类型代码,因此它实际上可以运行):

export class C {
  n : number;
  f1: number => number;
  f2: number => number;

  constructor(){
    this.n = 42;
    this.f1 = (x:number) => x + this.n;
    this.f2 = function (x:number) { return  x + this.n;};
  }
}

现在看看当我们使用来自不同模块的类 C 时会发生什么,如下所示:

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1: number = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2: number = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

如您所见,类型检查器在这里失败: f2 应该返回一个数字,但它返回了一个字符串!

更糟糕的是,似乎没有任何可以想象的类型检查器可以处理普通(非箭头)JavaScript函数,因为f2的“this”没有出现在f2的参数列表中,所以不可能添加“this”所需的类型作为 f2 的注释。

这个问题是否也会影响不使用类型检查器的人?我认为是这样,因为即使我们没有静态类型,我们也会认为它们就在那里。(“第一个参数必须是数字,第二个参数必须是字符串”等)隐藏的“this”参数可能会或可能不会在函数体中使用,这使我们的心理簿记变得更加困难。

这是可运行的无类型版本,将由 Babel 生成:

class C {
    constructor() {
        this.n = 42;
        this.f1 = x => x + this.n;
        this.f2 = function (x) { return x + this.n; };
    }
}

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1 = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2 = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

于 2017-03-12T00:35:33.427 回答
3

箭头函数或lambdas是在 ES 6 中引入的。除了它在最小语法上的优雅之外,最显着的功能差异是 this 箭头函数内部的作用域

正则函数表达式中,关键字根据调用它的上下文this绑定到不同的值。

箭头函数中,this词法绑定的,这意味着它this从定义箭头函数的范围(父范围)关闭,并且无论在何处以及如何调用/调用它都不会改变。

箭头函数作为对象方法的局限性

// this = global Window
let objA = {
  id: 10,
  name: "Simar",
  print () { // same as print: function()
    console.log(`[${this.id} -> ${this.name}]`);
  }
}

objA.print(); // logs: [10 -> Simar]

objA = {
  id: 10,
  name: "Simar",
  print: () => {
    // Closes over this lexically (global Window)
    console.log(`[${this.id} -> ${this.name}]`);
  }
};

objA.print(); // logs: [undefined -> undefined]

在使用常规定义方法的情况objA.print()下,它通过正确解析为方法调用来工作,但在定义为箭头函数时失败。这是因为在常规函数中,当作为对象 ( ) 的方法调用时,对象本身就是对象。print()functionthisobjA=>thisobjA

但是,在箭头函数的情况下,它this会在词法上绑定到this定义它的封闭范围(在我们的例子中是 global / Window),并在调用过程中保持它作为 on 的方法保持不变objA

与对象方法中的常规函数​​相比,箭头函数具有优势,仅限this于在定义时预期被固定和绑定的情况。

/* this = global | Window (enclosing scope) */

let objB = {
  id: 20,
  name: "Paul",
  print () { // Same as print: function()
    setTimeout( function() {
      // Invoked async, not bound to objB
      console.log(`[${this.id} -> ${this.name}]`);
    }, 1)
  }
};

objB.print(); // Logs: [undefined -> undefined]'

objB = {
  id: 20,
  name: "Paul",
  print () { // Same as print: function()
    setTimeout( () => {
      // Closes over bind to this from objB.print()
      console.log(`[${this.id} -> ${this.name}]`);
    }, 1)
  }
};

objB.print(); // Logs: [20 -> Paul]

在方法被定义为异步调用[${this.id} -> {this.name}]作为回调的函数的情况下objB.print(),正确解析为箭头函数用作回调时,但是当回调被定义为常规函数时失败。print()console.log()setTimeoutthisobjB

=>这是因为传递给的箭头函数在词法上从其父级setTimeout(()=>..)关闭,即定义它的调用。换句话说,箭头函数传入 to 到bound to as its因为调用 is本身。thisobjB.print()=>setTimeout(()==>...objBthisobjB.print() thisobjB

通过Function.prototype.bind()将回调绑定到正确的this.

const objB = {
  id: 20,
  name: "Singh",
  print () { // The same as print: function()
    setTimeout( (function() {
      console.log(`[${this.id} -> ${this.name}]`);
    }).bind(this), 1)
  }
}

objB.print() // logs: [20 -> Singh]

然而,箭头函数派上用场,并且在异步回调的情况下更不容易出错,我们知道this它获取和应该绑定的函数定义的时间。

this需要跨调用更改的箭头函数的限制

任何时候,我们都需要一个this可以在调用时更改的函数,我们不能使用箭头函数。

/* this = global | Window (enclosing scope) */

function print() {
  console.log(`[${this.id} -> {this.name}]`);
}

const obj1 = {
  id: 10,
  name: "Simar",
  print // The same as print: print
};

obj.print(); // Logs: [10 -> Simar]

const obj2 = {
  id: 20,
  name: "Paul",
};

printObj2 = obj2.bind(obj2);
printObj2(); // Logs: [20 -> Paul]
print.call(obj2); // logs: [20 -> Paul]

以上都不能与箭头函数const print = () => { console.log([${this.id} -> {this.name}]一起);}使用,因为this无法更改,并且将保持绑定到this定义它的封闭范围(全局/窗口)。

obj1在所有这些示例中,我们一个接一个地使用不同的对象 (和)调用相同的函数obj2,这两个对象都是在print()函数声明之后创建的。

这些都是人为的例子,但让我们考虑一些更真实的例子。如果我们必须编写reduce()类似于在 上工作的方法arrays,我们又不能将它定义为 lambda,因为它需要this从调用上下文(即调用它的数组)推断。

因此,构造函数永远不能定义为箭头函数,因为this构造函数不能在其声明时设置。每次使用new关键字调用构造函数时,都会创建一个新对象,然后将其绑定到该特定调用。

此外,当框架或系统接受稍后使用动态上下文调用的回调函数时this,我们不能使用箭头函数,因为this每次调用都可能需要更改。这种情况通常出现在 DOM 事件处理程序中。

'use strict'
var button = document.getElementById('button');

button.addEventListener('click', function {
  // web-api invokes with this bound to current-target in DOM
  this.classList.toggle('on');
});

var button = document.getElementById('button');

button.addEventListener('click', () => {
  // TypeError; 'use strict' -> no global this
  this.classList.toggle('on');
});

这也是为什么在Angular 2+Vue.js等框架中期望模板组件绑定方法是常规函数/方法的原因,this因为它们的调用由绑定函数的框架管理。(Angular 使用 Zone.js 来管理调用视图模板绑定函数的异步上下文。)

另一方面,在React中,当我们想要将组件的方法作为事件处理程序传递时,例如,<input onChange={this.handleOnchange} />我们应该handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}为每个调用定义一个箭头函数。我们希望它与为渲染的 DOM 元素生成 JSX 的组件实例相同。


这篇文章也可以在我的 Medium出版物中找到。如果您喜欢这篇文章,或者有任何意见和建议,请在Medium上拍手发表评论

于 2019-03-24T00:06:42.520 回答
3

我更喜欢在不需要访问本地this的任何时候都使用箭头函数,因为箭头函数不绑定自己的this、 arguments、 super 或 new.target

于 2017-05-02T08:51:44.613 回答
1

以一种简单的方式,

var a = 20; function a() {this.a = 10; console.log(a);}
//20, since the context here is window.

另一个例子:

var a = 20;
function ex(){
    this.a = 10;
    function inner(){
        console.log(this.a); // Can you guess the output of this line?
    }
    inner();
}
var test = new ex();

答:控制台会打印 20。

原因是每当执行一个函数时都会创建自己的堆栈,在此示例中,该ex函数是使用new运算符执行的,因此将创建一个上下文,并且在inner执行时 JavaScript 将创建一个新堆栈并在其中执行该inner函数global context是本地上下文。

因此,如果我们希望inner函数具有本地上下文,即ex,那么我们需要将上下文绑定到内部函数。

箭头解决了这个问题。Global context他们不采用,而是采用local context如果存在的话。在*给出的示例中,它将采用new ex()as this

因此,在绑定是显式的所有情况下,箭头默认解决问题。

于 2017-07-21T02:52:52.363 回答