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

lys*_*ing 379 javascript lambda ecmascript-harmony ecmascript-6 arrow-functions

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

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

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

  • 不要工作 new
  • 固定this在初始化时限制范围

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

  • "他们工作的每个地方",即在任何地方,函数不必对this变量不可知,我们不创建对象.
  • 只有"需要的任何地方",即事件监听器,超时,需要绑定到某个范围
  • 具有'短'功能但不具有'长'功能
  • 仅适用于不包含其他箭头功能的功能

我正在寻找的是在ECMAScript的未来版本中选择适当的函数符号的指南.该指南需要明确,以便可以向团队中的开发人员讲授,并保持一致,这样就不需要从一个函数符号到另一个函数符号进行不断的重构.

lys*_*ing 304

不久前,我们的团队将其所有代码(一个中型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
Run Code Online (Sandbox Code Playgroud)

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

function Person(name) {
    this.name = name;
}
Run Code Online (Sandbox Code Playgroud)

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

class Person {
    constructor(name) {
        this.name = name;
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望最终不鼓励使用前一种表示法.某些对象构造函数表示法仍然可以用于简单的匿名对象工厂,其中对象是以编程方式生成的,但其他情况并非如此.

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


箭头功能的可读性

坚持常规功能的可能最好的理由 - 范围安全被诅咒 - 箭头功能的可读性低于常规功能.如果您的代码首先不起作用,那么箭头函数似乎不是必需的,并且当箭头函数不一致使用时,它们看起来很难看.

ECMAScript已经发生了很大变化,因为ECMAScript 5.1为我们提供了功能Array.forEach,Array.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;
        })
}
Run Code Online (Sandbox Code Playgroud)

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

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));
}
Run Code Online (Sandbox Code Playgroud)

虽然任何一个箭头功能都可以用标准功能代替,但这样做几乎没有什么好处.哪个版本更具可读性?我会说第一个.

我认为,随着时间的推移,使用箭头功能或常规功能的问题将变得不那么重要.大多数函数要么成为类方法,要么取消function关键字,否则它们将成为类.函数将继续用于修补类Object.prototype.与此同时,我建议function为任何真正属于类方法或类的东西保留关键字.


笔记

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

  • 我实际上在实例3中认为,常规函数更具可读性.即使是非程序员也可以对正在发生的事情进行判断.使用箭头,您需要确切了解它们如何工作以理解该示例.也许更多新行将有助于箭头示例,但我不知道.只是我的2美分,但箭头让我畏缩(但我还没有使用它们,所以我很快就会被转换.) (11认同)
  • 另一次你想要使用`function`就是你不希望`this`被束缚,对吗?我最常见的情况是事件,你可能希望`this`引用触发事件的对象(通常是DOM节点). (4认同)
  • @Spencer这是一个公平的观点.根据我自己的经验,`=>`随着时间的推移最终看起来更好.我怀疑非程序员对这两个例子会有不同的看法.如果您正在编写ES2016代码,那么您通常不会最终使用这么多箭头函数.在这个例子中,使用async/await和数组理解,你最终只能在`reduce()`调用中使用一个箭头函数. (3认同)
  • 我完全同意Spencer的观点,即常规函数在该示例中更具可读性. (2认同)
  • 好的答案,谢谢!我个人也尽可能在全球范围内使用箭头.这让我几乎没有"功能".对我来说,代码中的"功能"意味着需要坚持并经过仔细考虑的特殊情况. (2认同)

Jac*_*son 79

根据该提案,箭头旨在"解决和解决传统的几个常见痛点" 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);
};
Run Code Online (Sandbox Code Playgroud)

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

this.pages.forEach(page => page.draw(this.settings));
Run Code Online (Sandbox Code Playgroud)

但是,请考虑代码是否使用了像jQuery这样的库,其方法this特别绑定.现在,有两个this值要处理:

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

我们必须使用function以便动态each绑定this.我们不能在这里使用箭头功能.

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

function Reader() {
    this.book.on('change', function () {
        this.reformat();
    });
}
Run Code Online (Sandbox Code Playgroud)

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

function Reader() {
    this.book.on('change', () => this.reformat());
}
Run Code Online (Sandbox Code Playgroud)

人们可能会提出这样的说法:"箭头有时可能是错误的使用功能吗?也许如果我们很少需要动态this值,那么大多数时候使用箭头仍然可以."

但是问问自己:"调试代码并发现错误的结果是由'边缘案例引起的'是否值得'?'"我宁愿避免麻烦,不仅在大多数时候,而且100%的时间.

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

function Reader() {
    var reader = this;
    reader.book.on('change', function () {
        var book = this;
        book.reformat();
        reader.reformat();
    });
}
Run Code Online (Sandbox Code Playgroud)

此外,始终分配this给变量(即使存在单个this或没有其他功能)也可确保即使在代码更改后,一个人的意图仍然清晰.

而且,动态this也不例外.jQuery用于超过5000万个网站(截至2016年2月撰写).以下是this动态绑定的其他API :

  • Mocha(昨天下载约120k)通过它公开测试方法this.
  • Grunt(昨天下载约63,000)公开了构建任务的方法this.
  • Backbone(昨天下载约22k)定义了访问方法this.
  • 事件API(如DOM的)指的是EventTargetthis.
  • 修补或扩展的原型API引用实例this.

(统计数据来自http://trends.builtwith.com/javascript/jQueryhttps://www.npmjs.com.)

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

this有时期望词汇,但有时不会; 正如this有时候会有动力,但有时却没有.值得庆幸的是,有一种更好的方法,它始终生成并传达预期的绑定.

关于简洁的语法

Arrow函数成功地为函数提供了"更短的语法形式".但这些较短的功能会让你更成功吗?

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

中线长度(每行55个字符)似乎支持正常和快速的有效读数.这产生了最高水平的理解...

类似的理由是针对条件(三元)运算符和单行if语句.

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

有人可能会说,"如果可能,只使用短版本的短版本怎么样?" 但是现在风格规则与语言约束相矛盾:"尽量使用最短的函数符号,记住有时只有最长的符号会this按预期绑定." 这种混淆使得箭头特别容易被误用.

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

const a = x =>
    doSomething(x);

const b = x =>
    doSomething(x);
    doSomethingElse(x);
Run Code Online (Sandbox Code Playgroud)

这两个函数在语法上都是有效的.但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);
};
Run Code Online (Sandbox Code Playgroud)

可以将作为rest参数的内容解析为扩展运算符:

processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest
Run Code Online (Sandbox Code Playgroud)

分配可能与默认参数混淆:

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
Run Code Online (Sandbox Code Playgroud)

块看起来像对象:

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

这是什么意思?

() => {}
Run Code Online (Sandbox Code Playgroud)

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

=>看起来像<=>=:

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

if (x => 1) {}
if (x >= 1) {}
Run Code Online (Sandbox Code Playgroud)

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

(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function
Run Code Online (Sandbox Code Playgroud)

虽然,如果一个人写(() => doSomething()());的目的是编写一个立即调用的函数表达式,那么根本不会发生任何事情.

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

语法function是无异常概括的.function单独使用意味着语言本身可以防止编写令人困惑的代码.为了编写在所有情况下应该在语法上理解的过程,我选择function.

关于指南

您需要一份需要"明确"和"一致"的指南.使用箭头函数最终将导致语法有效,逻辑无效的代码,两种函数形式交织在一起,有意义且任意.因此,我提供以下内容:

ES6中功能表示法的指南:

  • 始终使用function.创建程序.
  • 始终分配this给变量.不要用() => {}.

  • 写得很好!虽然我不同意你的大多数观点,但重要的是要考虑相反的观点. (11认同)
  • "拥有多种做事方式会为工作场所和语言社区的争论和分歧创造不必要的因素.如果语言语法不允许我们做出糟糕的选择,那就更好了." 同意这么多.好写!我认为箭头功能实际上是后退一步.在另一个主题上,我希望我的同事不再尝试使用一系列.prototype定义将JavaScript转换为C#.那真令人恶心.我应该匿名链接你的帖子:) (6认同)
  • 有趣的是写一个函数式程序员对JavaScript的看法.我不确定我是否同意私有变量参数.IMO很少有人真正需要它们; 那些做的人可能还需要其他合同功能,无论如何都要使用类似TypeScript的语言扩展.我当然可以看到"自我"的吸引力,而不是这个.你声明的箭头函数陷阱也是有效的,并且与其他语句相同的标准也可以在没有括号的情况下适用; 否则,我认为你的论点也可以在任何地方提倡箭头功能. (4认同)
  • 不是箭头函数,但`this`的奇怪行为是Javascript的问题.而不是隐式绑定,`this`应该作为显式参数传递. (4认同)
  • @rand完全是我的想法,虽然我会责怪jQuery和其他库滥用`this`而不是JavaScript来拥有`this`.看起来他们只是强迫你一个你无法命名的参数. (3认同)
  • "_始终使用函数(因此可以始终动态绑定),并始终通过变量引用此函数.".**我不能不同意!** (3认同)
  • 我修改了我的论文,以便更明确地说我反对在任何地方使用箭头函数:"在图片中使用`this`,箭头函数和`函数`几乎具有相同的值;除非由于其更严格的语法,`function`略胜一筹.所以总是使用它." (2认同)
  • 写得很好,我很喜欢阅读.然而,我不禁感到反对归结为对历史行为的偏好,而不引入新的行为,以及对新的简单不熟悉.这来自于那些首先讨厌胖箭头语法的人.但是语言应该保持不变,箭头功能确实可以解决问题.你不会帮助自己(在2018年)遵循本指南,从不使用箭头功能. (2认同)

Tha*_*var 36

创建箭头函数是为了简化功能scopethis通过使关键字变得更简单来解决关键字.它们使用的=>语法看起来像箭头.

注意:它不会替换现有功能.如果用箭头函数替换每个函数语法,它在所有情况下都不起作用.

让我们看看现有的ES5语法,如果this关键字位于对象的方法(属于对象的函数)中,它会引用什么?

var Actor = {
  name: 'RajiniKanth',
  getName: function() {
     console.log(this.name);
  }
};
Actor.getName();
Run Code Online (Sandbox Code Playgroud)

上面的代码片段将引用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();
Run Code Online (Sandbox Code Playgroud)

那么如果this关键字在里面method’s function呢?

在这里,这将指window objectinner function是它的堕落scope.因为this,对于这种情况,总是引用它所在函数的所有者 - 因为它现在超出了范围 - 窗口/全局对象.

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

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

fn(); // [object Window]
Run Code Online (Sandbox Code Playgroud)

有很多方法可以解决我们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();
Run Code Online (Sandbox Code Playgroud)

使用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();
Run Code Online (Sandbox Code Playgroud)

现在有了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();
Run Code Online (Sandbox Code Playgroud)

Arrow functions更像函数语句,只是他们bind的这parent scope.如果arrow function is in top scope,this参数将引用window/global scope,而常规函数内的箭头函数将使其this参数与其外部函数相同.

随着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
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,我们失去了对此的控制.我们可以通过使用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
Run Code Online (Sandbox Code Playgroud)

什么时候不去箭头功能

在对象文字内.

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

Actor.getName();
Run Code Online (Sandbox Code Playgroud)

Actor.getName与箭头函数来定义,但在调用它,因为警报未定义this.nameundefined作为上下文仍然window.

之所以会发生这种情况,是因为箭头函数用词汇方式将上下文与window object...即外部范围绑定在一起.执行this.name等同于window.name,未定义.

对象原型

在a上定义方法时适用相同的规则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
Run Code Online (Sandbox Code Playgroud)

调用构造函数

this在构造调用中是新创建的对象.当执行新的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!');
Run Code Online (Sandbox Code Playgroud)

使用动态上下文进行回调

箭头函数context在声明上静态绑定,不可能使其动态化.将事件侦听器附加到DOM元素是客户端编程中的常见任务.事件触发处理程序函数,并将此作为目标元素.

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

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

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

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

当用户单击按钮时,处理程序功能中的按钮就是按钮.因此,this.innerHTML = 'Clicked button'正确修改按钮文本以反映单击状态.

参考文献:https: //rainsoft.io/when-not-to-use-arrow-functions-in-javascript/

  • 你的代码在'使用bind来附加this关键字后引用方法的内部函数'.有错误.你测试过其余的例子了吗? (2认同)
  • 在最后一种情况下,使用“addEventListener”,即使使用箭头函数,您仍然可以使用“event.target”获取单击的范围。想法? (2认同)

Man*_*z90 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
Run Code Online (Sandbox Code Playgroud)

ES6版本:

var multiplyArrow = (a,b) => a*b;
console.log(multiplyArrow(5,6)); //30
Run Code Online (Sandbox Code Playgroud)

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

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

ES5版本:

var double = function(a) {
    return a*2;
};
console.log(double(2)); //4
Run Code Online (Sandbox Code Playgroud)

ES6版本:

var doubleArrow  = a => a*2;
console.log(doubleArrow(2)); //4
Run Code Online (Sandbox Code Playgroud)

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

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

ES5版本:

var sayHello = function() {
    console.log("Hello");
};
sayHello(); //Hello
Run Code Online (Sandbox Code Playgroud)

ES6版本:

var sayHelloArrow = () => {console.log("sayHelloArrow");}
sayHelloArrow(); //sayHelloArrow
Run Code Online (Sandbox Code Playgroud)

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

ES6版本:

var increment = x => {
  return x + 1;
};
console.log(increment(1)); //2
Run Code Online (Sandbox Code Playgroud)

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

ES6版本:

var returnObject = () => ({a:5});
console.log(returnObject());
Run Code Online (Sandbox Code Playgroud)

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

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

ES6版本:

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

注意: foo是ES5功能,与arguments像对象阵列和传递参数给它是2这样arguments[0]foo为2.

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
Run Code Online (Sandbox Code Playgroud)

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

undefined: Katty
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

注:传递给回调setTimeout是一个ES6箭头功能,它不具有它自己的this,因此它需要从它的外部背景下,是greetUser具有thisobj6因此我们得到的输出:

Hi, Welcome: Katty
Run Code Online (Sandbox Code Playgroud)

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


Jac*_*son 8

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

关于词汇 this

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

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

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

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

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

关于简洁的语法

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

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

关于指南

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

ES6 中的函数符号指南:

  • 不要使用this.
  • 对按名称调用的函数使用函数声明(因为它们被提升)。
  • 对回调使用箭头函数(因为它们往往更简洁)。

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

Car*_*ann 6

除了到目前为止的大答案之外,我想提出一个非常不同的理由,为什么箭头函数在某种意义上比"普通"JavaScript函数更好.为了便于讨论,让我们暂时假设我们使用类型检查器,如TypeScript或Facebook的"Flow".考虑以下玩具模块,它是有效的ECMAScript 6代码加上流类型注释:(我将包含无类型代码,这将在Babab的实际结果中,在此答案的最后,因此它实际上可以运行.)

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;};
  }
}
Run Code Online (Sandbox Code Playgroud)

现在看看当我们从不同的模块使用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!
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,类型检查器在这里失败: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!
Run Code Online (Sandbox Code Playgroud)