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

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

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

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

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

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

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


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

在全局作用域和Object中使用函数。原型属性。 为对象构造函数使用类。 在其他地方使用=>。

为什么几乎所有地方都使用箭头函数?

Scope safety: When arrow functions are used consistently, everything is guaranteed to use the same thisObject as the root. If even a single standard function callback is mixed in with a bunch of arrow functions there's a chance the scope will become messed up. Compactness: Arrow functions are easier to read and write. (This may seem opinionated so I will give a few examples further on.) Clarity: When almost everything is an arrow function, any regular function immediately sticks out for defining the scope. A developer can always look up the next-higher function statement to see what the thisObject is.

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

To indicate a function that should not access the thisObject. The window object (global scope) is best addressed explicitly. Many Object.prototype definitions live in the global scope (think String.prototype.truncate, etc.) and those generally have to be of type function anyway. Consistently using function on the global scope helps avoid errors. Many functions in the global scope are object constructors for old-style class definitions. Functions can be named1. This has two benefits: (1) It is less awkward to writefunction foo(){} than const foo = () => {} — in particular outside other function calls. (2) The function name shows in stack traces. While it would be tedious to name every internal callback, naming all the public functions is probably a good idea. Function declarations are hoisted, (meaning they can be accessed before they are declared), which is a useful attribute in a static utility function.

对象构造函数

尝试实例化箭头函数会抛出异常:

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

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

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

然而,功能上相同的ECMAScript Harmony草案类定义几乎同样紧凑:

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

我预计使用前一种表示法最终将被劝阻。对象构造函数符号可能仍然被一些简单的匿名对象工厂使用,在这些工厂中以编程方式生成对象,但并不用于其他地方。

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

箭头函数的可读性

坚持使用规则函数的最好理由可能是,箭头函数的可读性比规则函数差。如果你的代码从一开始就没有功能性,那么箭头函数似乎就没有必要了,当箭头函数没有被一致使用时,它们看起来就很难看。

自从ECMAScript 5.1给了我们函数数组之后,ECMAScript已经发生了很大的变化。forEach,数组。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关键字。


笔记

Named arrow functions have been deferred in the ES6 specification. They might still be added a future version. According to the draft specification, "Class declarations/expressions create a constructor function/prototype pair exactly as for function declarations" as long as a class does not use the extend keyword. A minor difference is that class declarations are constants, whereas function declarations are not. Note on blocks in single statement arrow functions: I like to use a block wherever an arrow function is called for the side effect alone (e.g., assignment). That way it is clear that the return value can be discarded.


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

然而,

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

因此,箭头函数容易引起混淆和错误,应该从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函数表示法指南:

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


创建箭头函数是为了简化函数范围,并通过简化this关键字来解决它。它们使用了=>语法,它看起来像一个箭头。

备注:不替换已有功能。如果你用箭头函数替换所有的函数语法,它不会在所有情况下都有效。

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

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

上面的代码片段将引用一个对象并打印出名称“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的函数中呢?

这里this指的是窗口对象,而不是内部函数,因为它超出了作用域。因此,总是引用它所在函数的所有者,在这种情况下——因为它现在超出了作用域——是window/global对象。

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

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

fn(); // [object Window]

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

通常你会在方法的内部函数之外创建一个变量。现在' forEach '方法可以访问这个对象,从而访问对象的属性及其值。

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关键字附加到该方法的内部函数。

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参数将与其外部函数相同。

对于箭头函数,它在创建时被绑定到外围作用域,并且不能更改。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();

演员。getName是用一个箭头函数定义的,但在调用时它警告为未定义,因为this.name是未定义的,因为上下文仍然是窗口。

发生这种情况是因为箭头函数在词法上将上下文绑定到窗口对象…也就是外部作用域。执行this.name相当于window.name,而window.name是未定义的。

对象原型

同样的规则也适用于在原型对象上定义方法。而不是使用箭头函数来定义sayCatName方法,这会带来一个不正确的上下文窗口:

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

调用构造函数

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

这是从封闭上下文(即外部作用域)中设置的,这使得它没有分配给新创建的对象。

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

带有动态上下文的回调

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

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

这是在全局上下文中定义的箭头函数中的窗口。当单击事件发生时,浏览器尝试调用带有按钮上下文的处理程序函数,但箭头函数不会更改其预定义上下文。这一点。innerHTML相当于window。innerHTML,没有任何意义。

你必须应用一个函数表达式,它允许改变这取决于目标元素:

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

当用户单击按钮时,处理程序函数中的这个就是按钮。因此这个问题。innerHTML = 'Clicked button'正确修改按钮文本,以反映单击状态。

参考文献

何时“不”使用箭头函数


除了到目前为止的精彩回答之外,我还想提出一个非常不同的原因,为什么箭头函数在某种意义上从根本上优于“普通”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,不是字符串!


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


简单来说,

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()如下所示。

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


箭头函数-迄今为止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调用箭头函数时,我们没有这个绑定。


在es6中引入了箭头函数或lambda。除了在最小语法上的优雅,最显著的函数差异是在箭头函数内的作用域

在正则函数表达式中,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()方法时,它通过将此正确地解析为objA来进行方法调用,但当定义为arrow=>函数时失败。这是因为在常规函数中,当它作为对象(objA)的方法调用时,它就是对象本身。

然而,在箭头函数的情况下,它在词法上绑定到它定义的封闭作用域的this(在我们的例子中是global / Window),并在objA上作为方法调用时保持不变。

在对象的方法中,箭头函数比常规函数有优势,但只有当它在定义时被固定和绑定时才有优势。

/* 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. log)的函数。id} -> {this.name}])异步地作为setTimeout上的回调,当箭头函数被用作回调时,该函数正确地解析为objB,但当回调被定义为常规函数时失败。

这是因为传递给setTimeout(()=>..)的arrow =>函数在词法上关闭了它的父函数,即调用定义它的objB.print()。换句话说,arrow =>函数传递给setTimeout(()==>…因为objB.print()的调用就是objB本身。

我们可以很容易地使用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 = 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. log])id} -> {this.name}]);}因为它不能被更改,并且将一直绑定到它所定义的外围作用域的this (global / Window)。

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

这些都是虚构的例子,但让我们考虑一些更真实的例子。如果必须将reduce()方法编写成类似于处理数组的方法,那么同样不能将其定义为lambda,因为它需要从调用上下文(即调用它的数组)推断出这一点。

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

此外,当框架或系统接受一个回调函数,并在以后使用动态上下文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这样的框架中,模板组件绑定方法是常规的函数/方法,因为它们的调用是由绑定函数的框架管理的。(Angular使用Zone.js来管理视图模板绑定函数调用的异步上下文。)

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


本文也可以在我的Medium出版物中找到。如果你喜欢这篇文章,或者有任何评论和建议,请在Medium上鼓掌或留下评论。


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

关于词汇this

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

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

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

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

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

关于简洁的语法

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

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

关于指导方针

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

ES6函数表示法指南:

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