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

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

不要在创建原型时使用新的和不能使用的 修正了初始化时绑定范围的问题

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

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

我正在寻找在未来版本的ECMAScript中选择适当的函数符号的指导方针。指导原则需要明确,以便可以在团队中教授给开发人员,并保持一致,以便不需要在一个函数符号和另一个函数符号之间来回不断地重构。

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


当前回答

箭头函数-迄今为止ES6最广泛使用的功能…

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

不应该使用箭头函数:

When we want function hoisting as arrow functions are anonymous. When we want to use this/arguments in a function as arrow functions do not have this/arguments of their own, they depend upon their outer context. When we want to use named function as arrow functions are anonymous. When we want to use function as a constructor as arrow functions do not have their own this. When we want to add function as a property in object literal and use object in it as we can not access this (which should be object itself).

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

变体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:箭头函数没有自己的参数(类似object的数组)。它们依赖于参数的外部上下文。

ES6版本:

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

注意:

foo是一个ES5函数,它有一个类似object的参数数组,传递给它的参数是2,所以foo的参数[0]是2。

abc是一个ES6的箭头函数,因为它没有自己的参数。因此,它输出foo的参数[0],而不是它的外部上下文。

变体7:箭头函数本身没有这个,它们依赖于外部上下文

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,在严格使用的环境中没有定义。因此,我们得到输出:

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中获取它,greetUser有this。它是obj6,因此我们得到输出:

Hi, Welcome: Katty

杂项:

我们不能对箭头函数使用new。 箭头函数没有原型属性。 当通过apply或call调用箭头函数时,我们没有这个绑定。

其他回答

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

然而,

不能始终在词汇上绑定它 箭头函数的语法是微妙和模糊的

因此,箭头函数容易引起混淆和错误,应该从JavaScript程序员的词汇表中排除,而专门用函数代替。

关于词汇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的属性的问题。已经有几种方法可以做到这一点:可以将它赋值给一个变量,使用bind,或者使用Array聚合方法上可用的第三个参数。然而,箭头似乎是最简单的解决方法,所以该方法可以像这样重构:

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

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

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

我们必须使用函数来动态地绑定每个对象。这里不能用箭头函数。

处理多个this值也会令人困惑,因为很难知道作者在说哪个:

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

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

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

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

但是问问你自己:“调试代码并发现错误的结果是由‘边缘情况’引起的,这样做‘值得’吗?’”我宁愿避免麻烦,不只是大部分时间,而是100%的时间。

有一个更好的方法:总是使用函数(这样总是可以动态绑定),并且总是通过变量引用它。变量是词法的,有很多名字。将它赋值给一个变量会让你的意图更明确:

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

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

此外,动态也并非例外。jQuery在超过5000万个网站上使用(截至2016年2月撰写本文时)。下面是其他动态绑定的api:

Mocha(昨天大约有12万下载量)通过这个公开了它的测试方法。 Grunt(昨天大约有63000次下载)通过此方法公开了构建任务的方法。 Backbone(昨天下载了大约22k)定义了访问它的方法。 事件api(像DOM一样)用这个引用EventTarget。 打了补丁或扩展的原型api引用的是带有这个的实例。

(统计数据可浏览http://trends.builtwith.com/javascript/jQuery和https://www.npmjs.com。)

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

这个词有时是预期的,但有时不是;就像动态一样,这有时是预期的,但有时不是。值得庆幸的是,有一种更好的方法,它总是生成并传递预期的绑定。

关于简洁的语法

箭头函数成功地为函数提供了“更短的语法形式”。但这些更短的职位会让你更成功吗?

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

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

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

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

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

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

const a = x =>
    doSomething(x);

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

这两个函数在语法上都是有效的。但doSomethingElse (x);不在b的主体中。它只是一个缩进不好的顶级语句。

当扩展为块形式时,不再有隐式的返回,可能会忘记恢复。但是这个表达式可能只是为了产生副作用,所以谁知道接下来是否需要显式返回呢?

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);
};

rest形参可以解析为展开运算符:

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

这是什么意思?

() => {}

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

=>看起来像<=和>=:

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()());为了编写一个立即调用的函数表达式,什么也不会发生。

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

函数的语法是非常一般化的。专门使用函数意味着语言本身可以防止编写令人困惑的代码。为了编写在所有情况下都能从语法上理解的过程,我选择了函数。

关于指导方针

你要求一个需要“清晰”和“一致”的指导方针。使用箭头函数最终会导致语法有效,逻辑无效的代码,两种函数形式交织在一起,有意义且任意。因此,我提出以下建议:

ES6函数表示法指南:

总是创建带有函数的过程。 总是把这个赋值给一个变量。不要使用()=>{}。

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

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

导出类C { N:数字; F1: number => number; F2: number => number; 构造函数(){ 这一点。N = 42; 这一点。F1 = (x:number) => x + this.n; 这一点。F2 =函数(x:number){返回x + this.n;}; } }

现在看看当我们使用来自不同模块的类C时会发生什么,就像这样:

let o = {f1: new C()。f1, f2: new C()。F2, n: "foo"}; 设n1: number = o.f1(1);// n1 = 43 Console.log (n1 === 43);/ /正确的 设n2: number = o.f2(1);// n2 = "1foo" Console.log (n2 === "1foo");// true,不是字符串!

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

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

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

下面是可运行的非类型化版本,由Babel生成:

C类{ 构造函数(){ 这一点。N = 42; 这一点。F1 = x => x + this.n; 这一点。F2 =函数(x){返回x + this.n;}; } } let o = {f1: new C()。f1, f2: new C()。F2, n: "foo"}; 设n1 = o.f1(1);// n1 = 43 Console.log (n1 === 43);/ /正确的 设n2 = o.f2(1);// n2 = "1foo" Console.log (n2 === "1foo");// true,不是字符串!

简单来说,

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将创建一个新的堆栈,并在全局上下文中执行内部函数,尽管有一个局部上下文中。

因此,如果我们想让内部函数有一个局部上下文,也就是ex,那么我们需要将上下文绑定到内部函数。

箭头可以解决这个问题。它们不采用全局上下文,而是采用本地上下文(如果存在的话)。在*给出的例子中,它将接受new ex()如下所示。

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

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

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

关于词汇this

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

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

我认为有些人不会遇到这种情况,但即使遇到这种情况的人,他们也一定会发现自己在代码库中工作,每个文件都会出现100次这种情况,合理的人所希望的只是一点点(或很多)损害控制。所以箭头在某种程度上是好的,当它们让糟糕的情况变得更好的时候。

即使使用箭头比不使用箭头更容易编写代码,但使用箭头的规则仍然非常复杂(参见:current thread)。因此,正如您所要求的那样,指导方针既不“清晰”也不“一致”。即使程序员知道箭头的模糊性,我认为他们还是会耸耸肩接受它们,因为词汇的价值盖过了它们。

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

关于简洁的语法

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

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

关于指导方针

考虑到中性箭头函数的可能性,以及简洁性值得追求,我提供了以下更宽松的指导原则:

ES6函数表示法指南:

不要用这个。 对按名称调用的函数使用函数声明(因为它们是提升的)。 回调时使用箭头函数(因为它们更简洁)。