ChatGPT解决这个技术问题 Extra ChatGPT

我什么时候应该在 ECMAScript 6 中使用箭头函数?

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

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

不要使用 new 并且在创建原型时不能使用

在初始化时修复了这个绑定到范围的问题

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

“它们工作的所有地方”,即函数不必对 this 变量不可知的任何地方,并且我们没有创建对象。

只有“需要它们的任何地方”,即需要绑定到某个范围的事件监听器、超时

具有“短”功能,但不具有“长”功能

仅适用于不包含另一个箭头函数的函数

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

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

您认为 Fixed this bound to scope at initialisation 是一种限制吗?
这是一个优势,但如果您计划在原始上下文之外重用该函数,它也可能是一个限制。例如,当通过 Object.prototype 动态地将函数添加到类时。我所说的“限制”是指更改 this 的值是您可以使用常规函数但不能使用箭头函数执行的操作。
老实说,我认为编码风格指南相当固执己见。不要误会我的意思,我认为它们很重要,但没有适合所有人的单一指南。
我不认为 Fixed this bound to scope at initialisation 是一个限制。 :) 看看这篇文章:exploringjs.com/es6/ch_arrow-functions.html
@thefourtheye,“限制”在这里的意思是“限制,因为一个愚蠢的自动代码翻译器不能盲目地用另一个替换一个并假设一切都会按预期运行”。

P
Peter Mortensen

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

在全局范围和 Object.prototype 属性中使用函数。

将类用于对象构造函数。

在其他任何地方使用 =>。

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

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

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

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

对象构造函数

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

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

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

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

但是,功能相同的2 ECMAScript Harmony draft class definition 几乎一样紧凑:

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

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

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

箭头函数的可读性

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

自从 ECMAScript 5.1 为我们提供了函数式 Array.forEachArray.map 和所有这些函数式编程特性以来,ECMAScript 发生了很大变化,这些特性让我们可以使用以前使用过 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 关键字。

笔记

命名箭头函数已在 ES6 规范中被推迟。他们可能仍会被添加到未来的版本中。根据规范草案,“类声明/表达式创建构造函数/原型对,与函数声明完全相同”,只要类不使用扩展关键字。一个小的区别是类声明是常量,而函数声明不是。关于单语句箭头函数中的块的注意事项:我喜欢在单独调用箭头函数的地方使用块(例如,赋值)。这样一来,很明显可以丢弃返回值。


您想使用 function 的其他时间是当您不希望 this 被绑定时,对吗?我最常见的场景是事件,您可能希望 this 引用触发事件的对象(通常是 DOM 节点)。
我实际上认为在示例 3 中,常规函数更具可读性。即使是非程序员也能猜到发生了什么。使用箭头,您需要确切地知道它们如何工作才能理解该示例。也许更多的换行符会帮助箭头示例,但我不知道。只是我的 2 美分,但箭头让我畏缩(但我还没有使用它们,所以我可能很快就会转变。)
@Spencer 这是一个公平的观点。根据我自己的经验,随着时间的推移,=> 看起来会更好。我怀疑非程序员对这两个示例的感受会大不相同。如果您正在编写 ES2016 代码,通常也不会使用这么多箭头函数。在此示例中,使用 async/await 和数组推导式,您最终会在 reduce() 调用中得到一个箭头函数。
我完全同意 Spencer 的观点,即常规函数在该示例中更具可读性。
好答案,谢谢!我个人也尽可能在全局范围内使用箭头。这让我几乎没有“功能”。对我来说,代码中的“函数”意味着需要突出并仔细考虑的特殊情况。
P
Peter Mortensen

根据the proposal,箭头旨在“解决和解决传统函数表达式的几个常见痛点”。他们打算通过在词法上绑定 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 月撰写本文时)。以下是动态绑定 this 的其他 API:

Mocha(昨天下载量约为 120k)通过 this 公开了其测试方法。

Grunt(昨天下载了约 63k 次)通过它公开了构建任务的方法。

Backbone(昨天下载了约 22k 次)定义了访问它的方法。

事件 API(如 DOM)通过 this 引用 EventTarget。

修补或扩展的原型 API 指的是带有 this 的实例。

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

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

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

关于简洁的语法

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

x => x * xfunction (x) { return x * x; }“更容易阅读”吗?也许是这样,因为它更有可能产生一个单一的、短的代码行。根据戴森的The influence of reading speed and line length on the effectiveness of reading from screen

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

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

但是,您真的在编写简单的数学函数advertised in the proposal吗?我的领域不是数学的,所以我的子程序很少如此优雅。相反,我经常看到箭头函数打破了列限制,并且由于编辑器或样式指南而换行到另一行,这使 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 中的函数表示法指南:

始终创建具有功能的程序。

始终将其分配给变量。不要使用 () => {}。


关于函数式程序员对 JavaScript 的看法的有趣文章。我不确定我是否同意私有变量的论点。 IMO 很少有人真正需要它们;那些这样做的人可能还需要其他合同功能,并且无论如何都会选择像 TypeScript 这样的语言扩展。我当然可以看到 self 而不是 this 的吸引力。您陈述的箭头函数陷阱也都是有效的,并且与其他可以不使用大括号的语句相同的标准也绝对适用于此;否则,我认为根据您的论点,人们也可以在任何地方提倡箭头功能。
“有多种做事方式会为工作场所和语言社区的争论和分歧创造不必要的载体。如果语言语法不允许我们做出糟糕的选择,那就更好了。”非常同意。不错的文案!我认为箭头函数实际上是退一步。在另一个主题上,我希望我的同事不要再尝试使用一系列 .prototype 定义将 JavaScript 转换为 C#。那真令人恶心。我应该匿名链接你的帖子:)
写得非常好!虽然我不同意你的大部分观点,但考虑相反的观点很重要。
不是箭头函数,而是 this 的奇怪行为是 Javascript 的问题。 this 不应被隐式绑定,而应作为显式参数传递。
“始终使用函数(因此始终可以动态绑定),并始终通过变量引用它。”。我不能再不同意了!
P
Peter Mortensen

创建 Arrow functions 是为了简化函数 scope 并通过使其更简单来解决 this 关键字。他们使用 => 语法,看起来像一个箭头。

注意:它不会取代现有的功能。如果你用箭头函数替换每个函数语法,它不会在所有情况下都有效。

让我们看一下现有的 ES5 语法。如果 this 关键字在对象的方法(属于对象的函数)中,它指的是什么?

var Actor = {
  name: 'RajiniKanth',
  getName: function() {
     console.log(this.name);
  }
};
Actor.getName();

上面的代码段将引用 object 并打印出名称 "RajiniKanth"。让我们探索下面的代码片段,看看这里会指出什么。

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

现在如果 this 关键字在 method’s function 内会怎样?

这里指的是 window object,而不是 inner function,因为它不属于 scope。因为 this,总是引用它所在函数的所有者,对于这种情况——因为它现在超出了范围——窗口/全局对象。

当它在 object 的方法内时,function 的所有者就是对象。因此 this 关键字被绑定到对象。然而,当它在函数内部时,无论是独立的还是在另一个方法中,它总是引用 window/global 对象。

var fn = function(){
  alert(this);
}

fn(); // [object Window]

在我们的 ES5 本身中有解决这个问题的方法。在深入探讨 ES6 箭头函数如何解决它之前,让我们先研究一下。

通常,您会在方法的内部函数之外创建一个变量。现在 ‘forEach’ 方法可以访问 this 以及 object’s 属性及其值。

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   var _this = this;
   this.movies.forEach(function(movie) {
     alert(_this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

使用 bind 将引用该方法的 this 关键字附加到 method’s inner function

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   }.bind(this));
  }
};

Actor.showMovies();

现在有了 ES6 箭头函数,我们可以用更简单的方式处理词法作用域问题。

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach((movie) => {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

箭头函数更像函数语句,除了它们绑定 this父范围。如果 箭头函数在顶部范围内,则 this 参数将引用 窗口/全局范围,而常规函数内的箭头函数将具有其 this参数与其外部函数相同。

arrow 函数 this 在创建时绑定到封闭的 scope 并且不能更改。 new 运算符、bind、call 和 apply 对此没有影响。

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`
  asyncFunction(o, function (param) {
  // We made a mistake of thinking `this` is
  // the instance of `o`.
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? false

在上面的例子中,我们失去了对此的控制。我们可以通过使用 this 的变量引用或使用 bind 来解决上面的示例。使用 ES6,管理 this 变得更容易,因为它绑定到 词法范围

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`.
  //
  // Because this arrow function is created within
  // the scope of `doSomething` it is bound to this
  // lexical scope.
  asyncFunction(o, (param) => {
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? true

何时不使用箭头函数

在对象文字内。

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  getName: () => {
     alert(this.name);
  }
};

Actor.getName();

Actor.getName 是用箭头函数定义的,但在调用时它会发出未定义警报,因为 this.nameundefined,因为上下文仍然是 window

发生这种情况是因为箭头函数将上下文与 window object... 进行词汇绑定,即外部作用域。执行 this.name 等价于未定义的 window.name

对象原型

prototype object 上定义方法时适用相同的规则。而不是使用箭头函数来定义 sayCatName 方法,这会带来不正确的 context window

function Actor(name) {
  this.name = name;
}
Actor.prototype.getName = () => {
  console.log(this === window); // => true
  return this.name;
};
var act = new Actor('RajiniKanth');
act.getName(); // => undefined

调用构造函数

this 在构造调用中是新创建的对象。执行 new Fn() 时,constructor Fn 的上下文是一个新对象:this instanceof Fn === true

this 是从封闭上下文设置的,即外部范围,使其不分配给新创建的对象。

var Message = (text) => {
  this.text = text;
};
// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');

动态上下文回调

箭头函数在声明时静态绑定 context,无法使其动态化。将事件侦听器附加到 DOM 元素是客户端编程中的常见任务。一个事件以 this 作为目标元素触发处理函数。

var button = document.getElementById('myButton');
button.addEventListener('click', () => {
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});

this 是在全局上下文中定义的箭头函数中的窗口。当单击事件发生时,浏览器会尝试使用按钮上下文调用处理函数,但箭头函数不会更改其预定义的上下文。 this.innerHTML 等价于 window.innerHTML 并且没有意义。

您必须应用一个函数表达式,它允许根据目标元素更改它:

var button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});

当用户单击按钮时,处理函数中的 this 就是按钮。因此 this.innerHTML = 'Clicked button' 正确地修改了按钮文本以反映点击状态。

参考

何时“不”使用箭头功能


好吧,我必须承认,“最好的在中间”。赞成声明,箭头函数将不涵盖任何可能的函数用例。它们实际上只是为了解决一部分常见问题。完全切换到它们将是矫枉过正。
@DmitriPavlutin:检查我更新的帖子,它收集了很多东西......也许我应该发布一个参考。
在“使用绑定将引用方法的 this 关键字附加到方法的内部函数”行之后的代码。有错误。您是否测试了其余的示例?
using bind to attach the this keyword that refers to the method to the method’s inner function. 有语法错误。
在您的最后一种情况下,使用 addEventListener,即使使用箭头函数,您仍然可以使用 event.target 获取点击范围。想法?
P
Peter Mortensen

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

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

不应使用箭头函数:

当我们想要函数提升时,箭头函数是匿名的。当我们想在函数中使用 this/arguments 作为箭头函数没有自己的 this/arguments 时,它们取决于它们的外部上下文。当我们想使用命名函数作为箭头函数是匿名的。当我们想使用函数作为构造函数时,箭头函数没有自己的this。当我们想在对象字面量中添加函数作为属性并在其中使用对象时,我们无法访问 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,因此 fooarguments[0] 是 2。

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

变体 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,所以它从它的外部上下文中获取它,即 greetUserthis。那是 obj6,因此我们得到输出:

Hi, Welcome: Katty

各种各样的:

我们不能将 new 与箭头函数一起使用。

箭头函数没有原型属性。

当通过 apply 或 call 调用箭头函数时,我们没有 this 的绑定。


J
Jackson

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

关于词汇this

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

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

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

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

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

关于简洁的语法

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

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

关于指导方针

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

ES6 中的函数表示法指南:

不要使用这个。

对按名称调用的函数使用函数声明(因为它们已被提升)。

使用箭头函数进行回调(因为它们往往更简洁)。


100% 同意底部的“ES6 函数表示法指南”部分 - 特别是提升和内联回调函数。很好的答案!
P
Peter Mortensen

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

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

导出类 C { n : number; f1:数字=>数字; f2:数字=>数字;构造函数(){ this.n = 42; this.f1 = (x:number) => x + this.n; this.f2 = function (x:number) { return x + this.n;}; } }

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

让 o = { f1: 新 C().f1, f2: 新 C().f2, n: "foo" };让 n1: number = o.f1(1); // n1 = 43 console.log(n1 === 43); // true let n2: number = o.f2(1); // n2 = "1foo" console.log(n2 === "1foo"); // 是的,不是字符串!

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

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

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

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

类 C { 构造函数() { this.n = 42; this.f1 = x => x + this.n; this.f2 = function (x) { return x + this.n; }; } } 让 o = { f1: 新 C().f1, f2: 新 C().f2, n: "foo" };让 n1 = o.f1(1); // n1 = 43 console.log(n1 === 43); // true let n2 = o.f2(1); // n2 = "1foo" console.log(n2 === "1foo"); // 是的,不是字符串!


P
Peter Mortensen

我更喜欢在不需要访问本地 this 的任何时候都使用箭头函数,因为箭头函数 do not bind their own this, arguments, super, or new.target


“超级”是字面意思吗?
P
Peter Mortensen

箭头函数或 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() 方法使用常规 function 定义时,它通过将 this 正确解析为 objA 来进行方法调用,但在定义为箭头=> 函数时失败。这是因为当作为对象 (objA) 的方法调用时,常规函数中的 this 是对象本身。

但是,在箭头函数的情况下,this 会在词法上绑定到定义它的封闭范围的 this(在我们的例子中是全局 / Window),并在调用期间保持它作为 { 3}。

与对象方法中的常规函数相比,箭头函数具有优势,仅当 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]

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

这是因为传递给 setTimeout(()=>..) 的箭头 => 函数从其父级词法上关闭了 this,即调用定义它的 objB.print()。换句话说,传递给 setTimeout(()==>... 的箭头 => 函数绑定到 objB 作为它的 this,因为 objB.print() this 的调用是 objB 本身。

通过将 Function.prototype.bind() 绑定到正确的 this,我们可以轻松地使用 Function.prototype.bind() 使定义为常规函数的回调工作。

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 = 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(全局/窗口)。

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

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

因此,constructor 函数永远不能定义为箭头函数,因为构造函数的 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 的组件实例相同。

这篇文章也可以在 my Medium 出版物中找到。如果您喜欢这篇文章,或有任何意见和建议,请在 Medium鼓掌或留下评论 .


P
Peter Mortensen

以一种简单的方式,

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 会创建一个新的尽管存在本地上下文,但在 global context 中堆栈并执行 inner 函数。

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

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

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