什么时候应该在ECMAScript 6中使用Arrow函数?


406

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

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

ES6中的箭头功能至少有两个限制:

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

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

  • “无论它们在哪里工作”,即在每个地方函数不必与this变量无关,我们也没有创建对象。
  • 仅需要绑定到特定范围的“任何需要它们的地方”,即事件侦听器,超时
  • 具有“短”功能但不具有“长”功能
  • 仅对于不包含另一个箭头功能的功能

我正在寻找的是在将来的ECMAScript版本中选择适当的功能符号的指南。指南必须明确,以便可以向团队中的开发人员讲授,并且要保持一致,以便不需要从一个功能符号到另一个功能符号之间不断地来回重构。


33
您认为Fixed this bound to scope at initialisation限制吗?
thefourtheye 2014年

12
这是一个优点,但是如果您打算在原始上下文之外重用该函数,那么它也可能是一个限制。例如,当通过Object.prototype将函数动态添加到类中时。我所说的“限制”是指this您可以使用常规函数来执行更改值,而不能使用箭头函数来执行更改。
lyschoening 2014年

1
坦率地说,我认为编码风格准则是自以为是的。不要误会我的意思,我认为它们很重要,但是没有一个适合所有人的指南。
Felix Kling 2014年

我认为这不是Fixed this bound to scope at initialisation一个限制。:)看一下本文: exploringjs.com/es6/ch_arrow-functions.html
NgaNguyenDuy

3
@thefourtheye,“限制”在这里的意思是“限制,因为一个愚蠢的自动代码翻译器不能盲目地将另一个替换,并假设一切都会按预期运行”。
Pacerier '16

Answers:


322

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

  • 使用function在全球范围和Object.prototype性质。
  • 使用class的对象构造。
  • =>在其他地方使用。

为什么几乎在所有地方都使用箭头功能?

  1. 示波器安全性:始终使用箭头功能时,保证所有内容都使用与thisObject根相同的内容。如果什至将单个标准函数回调与一堆箭头函数混在一起,则范围可能会混乱。
  2. 紧凑性:箭头功能更易于读写。(这似乎是自以为是的,所以我将进一步举例说明)。
  3. 清晰度:当几乎所有东西都是箭头功能时,任何常规都会function立即伸出来定义范围。开发人员始终可以查找下一个较高的function语句,以查看其含义thisObject

为什么总是在全局作用域或模块作用域上使用常规函数?

  1. 指示不应访问的功能thisObject
  2. window对象(全局范围)是最好的明确处理。
  3. 许多Object.prototype定义都存在于全局范围内(例如,思考String.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 ES Harmony 草案类定义几乎一样紧凑:

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

我希望最终不鼓励使用前一种表示法。一些对象仍可以将对象构造函数表示法用于简单的匿名对象工厂,在这些对象中以编程方式生成对象,而其他方面则不多。

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


箭头功能的可读性

坚持使用常规功能最好的论据(该死的示波器安全性)将是,箭头功能不如常规功能易读。如果您的代码最初没有功能,那么箭头功能似乎就不是必需的,并且当箭头功能未得到一致使用时,它们看起来很丑陋。

自ECMAScript 5.1向我们提供了function以来,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. 关于单条语句箭头函数中的块的注意事项:我希望在仅因副作用而调用箭头函数的地方使用块(例如赋值)。这样,很明显可以丢弃返回值。

4
另一个您想使用的时间function是不想this被约束时,对吗?我最常见的情况是事件,您可能希望this引用触发事件的对象(通常是DOM节点)。
Brett 2014年

13
我实际上认为在示例3中,常规函数更具可读性。甚至非程序员也可以了解正在发生的事情。使用箭头,您需要确切了解它们如何工作以理解该示例。也许更多的换行符将有助于箭头示例,但我不知道。只是我的2美分,但箭使我感到畏缩(但我还没有使用过,所以我可能很快就会转换。)
Spencer

3
@Spencer这是一个公平的观点。根据我自己的经验,=>最终会随着时间的推移变得更好。我怀疑非程序员对这两个示例会有不同的感觉。如果要编写ES2016代码,通常也不会使用这么多箭头功能。在此示例中,使用async / await和数组推导,您最终只能在reduce()调用中使用一个箭头功能。
2015年

3
我完全同意Spencer的观点,在该示例中,常规函数的可读性更高。
jonschlinkert

2
好答案,谢谢!我个人也尽可能在全球范围内使用箭头。这使我几乎没有“功能”。对我来说,代码中的“功能”意味着特殊情况,需要坚持并仔细考虑。
kofifus

80

根据提议,箭的目的是“解决和解决传统的几个常见痛点” Function Expression。他们打算通过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给变量,use bind或使用Array聚合方法上可用的第3个参数。但是箭头似乎是最简单的解决方法,因此可以像这样重构方法:

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?如果将处理程序更改为箭头函数,我们将同样想知道作者是否想要dynamic this,但选择了箭头,因为它适合一行:

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

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

但请问问自己:“调试代码并发现错误的结果是由'edge case'带来的'值得'吗?”我希望不仅在大多数时候避免麻烦,而且希望避免麻烦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也不是例外。截至2017年2月,jQuery已在超过5000万个网站上使用。以下是其他this动态绑定的API :

  • Mocha(昨天下载了约120k)通过公开了用于测试的方法this
  • Grunt(昨天下载量约为63k)通过公开了构建任务的方法this
  • 骨干网(昨天下载约22k)定义了访问方法this
  • 事件API(如DOM的)引用EventTargetwith this
  • 修补或扩展的原型API引用带有的实例this

(通过http://trends.builtwith.com/javascript/jQueryhttps://www.npmjs.com进行的统计。)

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

this有时会期望词汇,但有时不会;this有时会产生动态变化,但有时却不会。值得庆幸的是,有一种更好的方法,它总是产生并传达期望的绑定。

关于简洁的语法

箭头功能成功地为功能提供了“较短的语法形式”。但是,这些较短的功能会使您更成功吗?

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

中等的行长(每行55个字符)似乎支持正常和快速的有效阅读。这产生了最高的理解力。。。

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

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

一个人可能会说:“如果可能的话,仅将简短版本用于简短功能?” 但是现在,一条样式规则与语言约束相矛盾:“请尝试使用可能的最短函数表示法,请记住,有时只有最长的表示法会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 parens

块看起来像对象:

(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给变量。不要使用() => {}

5
有趣的内容是关于函数式程序员对JavaScript的看法。我不确定我是否同意私有变量的说法。IMO很少有人真正需要它们。那些这样做的人可能还需要其他合同功能,并且无论如何都需要像TypeScript这样的语言扩展。我当然可以看到a self而不是this 的吸引力。您声明的箭头函数陷阱也都是有效的,并且与其他无需大括号即可使用的语句相同的标准在这里也同样适用;否则,我认为根据您的论点,在任何地方都可以提倡箭头功能。
lyschoening

7
“有多种做事方式会在工作场所和语言社区中引起不必要的争论和分歧。如果语言语法不允许我们做出错误的选择,那就更好了。” 非常同意。不错的文章!我认为箭头功能实际上是后退一步。在另一个主题上,我希望我的同事不要再尝试通过一系列.prototype定义将JavaScript转换为C#。那真令人恶心。我应该匿名链接您的帖子:)
Spencer

11
写得很好!尽管我不同意您的大部分观点,但重要的是要考虑相反的观点。
minexew '16

4
不是箭头功能,而是thisJavascript的问题。不应隐式绑定,而this应将其作为显式参数传递。
鲍勃,2013年

5
始终使用函数(因此可以始终对其进行动态绑定),并始终通过变量对其进行引用。 ”。我完全不同意!

48

创建箭头函数是为了简化功能scopethis通过使其更简单来解决关键字。他们利用=>语法,看起来像箭头。

注意:它不会替代现有功能。如果将所有函数语法替换为箭头函数,则并非在所有情况下都起作用。

让我们看一下现有的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 objectinner function是它掉出来了scope。因为this,总是引用函数所在的所有者,在这种情况下(因为它现在不在范围内),它是窗口/全局对象。

当它在object的方法中时- function的所有者是对象。因此,此关键字绑定到对象。但是,当它位于函数内部时,无论是独立存在还是在另一种方法中,它将始终引用该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箭头功能,我们可以lexical scoping以更简单的方式处理问题。

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

Actor.showMovies();

Arrow functions更像是函数语句,除了它们bind的this parent scope。如果arrow function is in top scopethis参数引用window/global scope,则常规函数内部的箭头函数的this参数与其外部函数相同。

With arrow函数在创建时this绑定到附件scope,无法更改。新的运算符,绑定,调用和应用对此无效。

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绑定的lexical scoping

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

之所以发生这种情况,是因为arrow函数将上下文与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元素是客户端编程中的常见任务。事件触发以此为目标元素的处理函数。

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

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

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

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

当用户单击按钮时,处理程序功能中的该按钮为按钮。从而this.innerHTML = 'Clicked button'正确修改按钮文本以反映单击状态。

参考:https : //dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/


好吧,我必须承认,“最好的在于中间”。赞成声明,箭头功能将不涵盖任何可能的功能用例。它们的真正目的是仅解决部分常见问题。完全切换到它们将是一个过大的杀伤力。
BlitZ

@DmitriPavlutin:检查我更新的帖子,它收集了很多东西...可能是我应该发布参考。
Thalaivar

2
“使用bind将引用该方法的this关键字附加到该方法的内部函数”行之后的代码。有错误。您是否测试了其余示例?
艾萨克·帕克

一个using bind to attach the this keyword that refers to the method to the method’s inner function.有语法错误。
Coda Chang

应该是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();
Coda Chang

14

箭头功能-迄今为止使用最广泛的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]foo为2。

abc是一个ES6 Arrow功能,因为它不具有它自己的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,因此它需要从它的外部背景下,是greetUser具有thisobj6因此我们得到的输出:

Hi, Welcome: Katty

杂项: 我们不能new与箭头功能一起使用。箭头功能不具有prototype属性。this通过apply或调用箭头函数时,我们没有绑定call


6

除了到目前为止的好答案之外,我还要提出一个非常不同的原因,为什么箭头函数在某种意义上要比“普通” 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!



3

我仍然支持在该主题的第一个答案中写的所有内容。但是,自那时以来,我对代码风格的看法有所发展,因此我对这个问题的新答案以我的上一个为基础。

关于词汇 this

在我的最后一个答案中,我故意避开了我对这种语言的基本信念,因为它与我所提出的论点没有直接关系。尽管如此,如果没有明确说明这一点,我将理解为什么许多人发现箭头是如此有用时,却只是拒绝我的建议不使用箭头。

我的信念是:我们不应该首先使用this它。因此,如果一个人故意避免this在其代码中使用,那么this箭头的“词汇”功能几乎没有价值。而且,在this以坏事为前提的情况下,箭的对待不算this是“好事”;相反,它更多是另一种不良语言功能的损害控制形式。

我认为有些人不会遇到这种情况,但是即使对于那些这样做的人,他们也必须始终发现自己正在代码库中工作,this每个文件出现一百次,并且几乎(或很多)损害控制已全部存在。一个合理的人可以希望的。因此,从某种程度上讲,当情况变得更好时,箭头可能是好的。

即使使用this带箭头的代码比不带箭头的代码容易编写,使用箭头的规则仍然非常复杂(请参阅:当前线程)。因此,根据您的要求,准则既不是“明确的”也不是“一致的”。即使程序员知道箭头的模糊性,我仍然认为它们耸了耸肩并接受了它们,因为词汇的价值this掩盖了它们。

所有这一切都是以下实现的序言:如果不使用this,那么this箭头通常引起的歧义就变得无关紧要。在这种情况下,箭头变得更加中立。

关于简洁的语法

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

换句话说:该死,我也想要一线功能!

关于指南

由于具有- this中性箭头功能的可能性,以及简洁性值得追求,因此我提供了以下更为宽松的准则:

ES6中的功能符号指南:

  • 不要使用this
  • 对要按名称调用的函数使用函数声明(因为它们已被吊起)。
  • 使用箭头函数进行回调(因为它们倾向于更短)。

100%同意您底部的“ ES6中的功能符号指南”部分-特别是对于提升和内联回调函数。好答案!
Jeff McCloud

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执行时,JS将创建一个新的堆栈并执行该inner函数,global context尽管存在当地情况。

因此,如果我们希望inner函数具有局部上下文,ex则需要将上下文绑定到内部函数。

箭头解决了这个问题,而不是Global context采取local context如果存在的话。在given example,它将new ex()作为this

因此,在所有绑定都是显式的情况下,默认情况下,Arrows可以解决问题。


1

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

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

arrow函数中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为方法调用,但是当定义为箭头失败=>的功能。这是因为this在常规函数中,作为对象(objA)上的方法调用时,对象本身就是对象。但是,在使用箭头函数的情况下,this将其按词法绑定到this定义它的封闭范围(在本例中为global / Window)的范围,并在调用过程中保持不变,与on上的方法相同objA

仅当this预期在时间定义上固定并绑定时,箭头功能相对于对象BUT方法中的常规功能的优点。

/* 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当被用作回叫的箭头功能,但未能当回调被定义为常规函数时。这是因为arrow =>函数从其父类(即)按词法传递到了setTimeout(()=>..)关闭状态this。调用objB.print()定义了它。在其他词,箭头=>功能传递给setTimeout(()==>...必将objBthis,因为在调用objB.print() thisobjB它本身。

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

const objB = {
 id: 20,
 name: "Singh",
 print () { // 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 // 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()声明该函数之后创建的。

这些是人为的例子,但让我们考虑一些更现实的例子。如果我们必须编写reduce()类似于的方法arrays ,则我们又不能将其定义为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);}为每次调用将其定义为箭头函数,我们希望它与生成用于渲染的JSX的组件相同DOM元素。


我的中号出版物上也有此文章。如果您喜欢机枪,或者有任何意见和建议,请在Medium鼓掌发表评论

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.