When should I use Arrow functions in ECMAScript 6?
这个问题是针对那些在即将到来的EcmaScript6(Harmony)环境中考虑过代码风格并且已经使用过该语言的人。
使用
ES6中的箭头函数至少有两个限制:
- 不要和
new 合作 - 固定的
this 在初始化时绑定到作用域
抛开这两个限制,理论上箭头函数几乎可以在任何地方取代常规函数。在实践中使用它们的正确方法是什么?是否应使用箭头功能,例如:
- "它们在任何地方工作",也就是说,在任何地方,函数都不必对
this 变量不可知,我们也不创建对象。 - 只有"它们需要的任何地方",即事件侦听器、超时,需要绑定到特定范围
- 具有"短"功能,但不具有"长"功能
- 仅与不包含其他箭头函数的函数一起使用
我要寻找的是在未来版本的ecmascript中选择适当的函数符号的指南。该指导方针需要清晰,以便能够向团队中的开发人员传授,并且保持一致,这样就不需要在一个函数表示法和另一个函数表示法之间来回不断地重构。
不久前,我们的团队将其所有代码(中型AngularJS应用程序)迁移到使用tracerbabel编译的javascript中。我现在对ES6及更高版本的函数使用以下经验法则:好的。
- 在全局范围内使用
function 和用于Object.prototype 属性。 - 对象构造函数使用
class 。 - 在其他地方使用
=> 。
为什么几乎在任何地方都使用箭头函数?好的。
为什么总是在全局范围或模块范围内使用常规函数?好的。
对象构造函数好的。
试图实例化arrow函数时引发异常:好的。
1 2 | var x = () => {}; new x(); // TypeError: x is not a constructor |
因此,与箭头函数相比,函数的一个主要优点是函数可以作为对象构造函数使用:好的。
1 2 3 | function Person(name) { this.name = name; } |
号
但是,功能上相同的2 ES Harmony草稿类定义几乎是紧凑的:好的。
1 2 3 4 5 | class Person { constructor(name) { this.name = name; } } |
我预计使用前一个符号最终会受到阻碍。对象构造函数符号可能仍然被一些简单的匿名对象工厂所使用,这些工厂中的对象是以编程方式生成的,但对于其他工厂则不是如此。好的。
如果需要对象构造函数,应该考虑将函数转换为上面所示的
箭头函数的可读性好的。
坚持常规函数的最好理由可能是箭头函数的可读性不如常规函数。如果您的代码一开始不起作用,那么箭头函数可能看起来不必要,并且如果箭头函数不被一致使用,它们看起来很难看。好的。
ECMAScript在ECMAScript 5.1给我们提供了函数
例如,这段代码(特别令人困惑)3:好的。
1 2 3 4 5 6 7 8 9 10 | 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; }) } |
具有常规函数的同一段代码:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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)); } |
号
虽然任何一个箭头函数都可以被一个标准函数替换,但是这样做的好处很小。哪个版本更可读?我会说第一个。好的。
我认为,随着时间的推移,使用箭头函数还是常规函数的问题将变得不那么重要。大多数函数要么成为类方法,使用
笔记好的。
好啊。
根据该提案,arrows的目标是"解决和解决传统
然而,好的。
- 不能在词汇上一致地绑定
this 。 - 箭头函数语法微妙且不明确
因此,arrow函数为混淆和错误创造了机会,应该从javascript程序员的词汇表中排除,只替换为
关于词汇
1 2 3 4 5 6 7 8 9 | function Book(settings) { this.settings = settings; this.pages = this.createPages(); } Book.prototype.render = function () { this.pages.forEach(function (page) { page.draw(this.settings); }, this); }; |
arrow函数旨在解决需要在回调中访问
1 | this.pages.forEach(page => page.draw(this.settings)); |
号
但是,考虑代码是否使用了jquery之类的库,其方法专门绑定
1 2 3 4 5 6 7 | Book.prototype.render = function () { var book = this; this.$pages.each(function (index) { var $page = $(this); book.draw(book.currentPage + index, $page); }); }; |
为了使
处理多个
1 2 3 4 5 | function Reader() { this.book.on('change', function () { this.reformat(); }); } |
。
作者真的打算给
1 2 3 | function Reader() { this.book.on('change', () => this.reformat()); } |
人们可能会摆出这样的姿势:"箭头有时可能是错误的功能,这是例外吗?也许,如果我们很少需要动态的
但是问问自己:"调试代码并发现错误的结果是由"边缘情况"引起的,这是值得的吗?"我宁愿避免麻烦,不只是大部分时间,而是100%的时间。好的。
有一种更好的方法:总是使用
1 2 3 4 5 6 7 8 | function Reader() { var reader = this; reader.book.on('change', function () { var book = this; book.reformat(); reader.reformat(); }); } |
。
此外,总是将
另外,动态
- Mocha(昨天下载量约12万)通过
this 公开了其测试方法。 - Grunt(昨天下载了约63K)通过
this 公开了构建任务的方法。 - 主干网(昨天下载了约22K)定义了访问
this 的方法。 - 事件API(类似于DOM)指的是带有
this 的EventTarget 。 - 修补或扩展的原型API指的是使用
this 的实例。
(通过http://trends.builtwith.com/javascript/jquery和https://www.npmjs.com进行统计。)好的。
您可能已经需要动态
词汇
关于简明扼要的语法好的。
arrow函数成功地为函数提供了"更短的语法形式"。但是这些较短的功能会让你更成功吗?好的。
A medium line length (55 characters per line) appears to support effective reading at normal and fast speeds. This produced the highest level of comprehension . . .
Ok.
号
对条件(三元)运算符和单行
然而,你真的在写提案中宣传的简单数学函数吗?我的域不是数学的,所以我的子程序很少如此优雅。相反,我通常会看到箭头函数打破了列限制,并由于编辑器或样式指南而换行,这会使Dyson定义的"可读性"无效。好的。
有人可能会摆出这样的姿势:"如果可能的话,只使用简短的函数怎么样?"但是现在,一个风格规则与语言约束相矛盾:"尽量使用最短的函数符号,记住有时只有最长的符号才能像预期的那样绑定
箭头函数语法有许多问题:好的。
1 2 3 4 5 6 | const a = x => doSomething(x); const b = x => doSomething(x); doSomethingElse(x); |
这两个函数在语法上都是有效的。但是
当扩展到块形式时,就不再有一个隐式的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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参数的内容解析为spread运算符:好的。
1 2 | processData(data, ...results => {}) // Spread processData(data, (...results) => {}) // Rest |
赋值可能与默认参数混淆:好的。
1 2 3 4 5 | 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 |
。
块看起来像对象:好的。
1 2 3 | (id) => id // Returns `id` (id) => {name: id} // Returns `undefined` (it's a labeled statement) (id) => ({name: id}) // Returns an object |
这是什么意思?好的。
1 | () => {} |
号
作者是否打算创建一个no op或返回空对象的函数?(考虑到这一点,我们是否应该将
1 2 3 4 5 | x => 1 ? 2 : 3 x <= 1 ? 2 : 3 if (x => 1) {} if (x >= 1) {} |
要立即调用箭头函数表达式,必须将
1 2 | (() => doSomething()()) // Creates function calling value of `doSomething()` (() => doSomething())() // Calls the arrow function |
。
但是,如果编写
考虑到上述所有情况,很难说arrow函数"更容易理解"。我们可以学习使用这种语法所需的所有特殊规则。它真的值得吗?好的。
关于指导方针好的。
您需要一个"清晰"和"一致"的指导原则。使用箭头函数最终将导致语法上有效、逻辑上无效的代码,两种函数形式交织在一起,有意义且任意。因此,我提出以下建议:好的。ES6中的功能符号指南:
- 始终使用
function 创建过程。 - 始终将
this 赋给变量。不要使用() => {} 。
好啊。
创建箭头函数是为了简化函数
注意:它不会取代现有的功能。如果用箭头函数替换每个函数语法,它就不会在所有情况下都起作用。好的。
让我们看一下现有的ES5语法,如果
1 2 3 4 5 6 7 | var Actor = { name: 'RajiniKanth', getName: function() { console.log(this.name); } }; Actor.getName(); |
上述代码段将引用
1 2 3 4 5 6 7 8 9 10 11 | var Actor = { name: 'RajiniKanth', movies: ['Kabali', 'Sivaji', 'Baba'], showMovies: function() { this.movies.forEach(function(movie) { alert(this.name +" has acted in" + movie); }); } }; Actor.showMovies(); |
号
那么,如果
这里指的是
当它在
1 2 3 4 5 | var fn = function(){ alert(this); } fn(); // [object Window] |
在我们的
通常,您会在方法的内部函数之外创建一个变量。现在,
1 2 3 4 5 6 7 8 9 10 11 12 | 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(); |
。
使用
1 2 3 4 5 6 7 8 9 10 11 | 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(); |
现在使用
1 2 3 4 5 6 7 8 9 10 11 | var Actor = { name: 'RajiniKanth', movies: ['Kabali', 'Sivaji', 'Baba'], showMovies: function() { this.movies.forEach((movie) => { alert(this.name +" has acted in" + movie); }); } }; Actor.showMovies(); |
。
对于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
。
在上面的例子中,我们失去了对这个的控制。我们可以通过使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
当不到箭头功能时
在对象文本中。好的。
1 2 3 4 5 6 7 8 9 | var Actor = { name: 'RajiniKanth', movies: ['Kabali', 'Sivaji', 'Baba'], getName: () => { alert(this.name); } }; Actor.getName(); |
。
发生这种情况的原因是arrow函数在词汇上与
prototype对象>
方法applies when the same rule defining在
1 2 3 4 5 6 7 8 9 | 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 |
invoking constructors>
在建设中
1 2 3 4 5 | var Message = (text) => { this.text = text; }; // Throws"TypeError: Message is not a constructor" var helloMessage = new Message('Hello World!'); |
动态语境与回调>
the function binds箭头
1 2 3 4 5 | var button = document.getElementById('myButton'); button.addEventListener('click', () => { console.log(this === window); // => true this.innerHTML = 'Clicked button'; }); |
You have to apply to which a function expression change,this on the allows depending元目标:>
1 2 3 4 5 | var button = document.getElementById('myButton'); button.addEventListener('click', function() { console.log(this === button); // => true this.innerHTML = 'Clicked button'; }); |
当用户点击the button,this is the button在处理函数。thus
references。rainsoft.io HTTPS:///when not -使用-箭头- / -函数的JavaScript>
好吧。
Arrow functions - most widely used ES6 feature so far ...
号
用法:除以下情况外,所有ES5函数都应替换为ES6箭头函数:
不应使用箭头函数:
- 因为箭头函数是匿名的。
- 由于arrow函数本身没有
this /arguments ,因此它们依赖于它们的外部上下文。
- 因为箭头函数是匿名的。
- 因为arrow函数没有自己的
this 。
- 因为我们不能访问
this (应该是对象本身)。
让我们了解箭头函数的一些变体,以便更好地了解:
变量1:当我们想向一个函数传递多个参数并从中返回一些值时。
ES5版本:
1 2 3 4 | var multiply = function (a,b) { return a*b; }; console.log(multiply(5,6)); //30 |
ES6版本:
1 2 | var multiplyArrow = (a,b) => a*b; console.log(multiplyArrow(5,6)); //30 |
号
注:不需要
变量2:当我们只想向一个函数传递一个参数并从中返回一些值时。
ES5版本:
1 2 3 4 | var double = function(a) { return a*2; }; console.log(double(2)); //4 |
ES6版本:
1 2 | var doubleArrow = a => a*2; console.log(doubleArrow(2)); //4 |
。
注:当只传递一个参数时,我们可以省略括号
变量3:当我们不想向函数传递任何参数并且不想返回任何值时。
ES5版本:
1 2 3 4 | var sayHello = function() { console.log("Hello"); }; sayHello(); //Hello |
ES6版本:
1 2 | var sayHelloArrow = () => {console.log("sayHelloArrow");} sayHelloArrow(); //sayHelloArrow |
。
变量4:当我们想要从箭头函数显式返回时。
ES6版本:
1 2 3 4 | var increment = x => { return x + 1; }; console.log(increment(1)); //2 |
。
变量5:当我们想要从箭头函数返回一个对象时。
ES6版本:
1 2 | var returnObject = () => ({a:5}); console.log(returnObject()); |
注:我们需要将对象括在括号
变体6:arrow函数本身没有
ES6版本:
1 2 3 4 5 | function foo() { var abc = i => arguments[0]; console.log(abc(1)); }; foo(2); // 2 |
。
注:
变体7:arrow函数本身没有
ES5版本:
1 2 3 4 5 6 7 8 9 10 | 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函数,它有自己的
1 | undefined: Katty |
。
ES6版本:
1 2 3 4 5 6 7 8 9 | 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 |
号
注:传递给
1 | Hi, Welcome: Katty |
号
其他:我们不能将
除了迄今为止的伟大答案,我还想提出一个非常不同的原因,为什么箭头函数在某种意义上比"普通"的javascript函数要好。为了便于讨论,让我们暂时假设我们使用类型检查器,如typescript或facebook的"流"。考虑下面的toy模块,它是有效的ecmascript 6代码加上流类型注释:(我将包括非类型化的代码,这实际上是babel的结果,在这个答案的最后,所以它可以实际运行。)
1 2 3 4 5 6 7 8 9 10 11 | 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时会发生什么,如下所示:
1 2 3 4 5 | 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生成:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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! |
我喜欢在不需要访问本地
函数或从箭头(6),在ES。在除了其优雅的语法功能差最小,最著名的是一个函数的范围内
In regular function expressions, the
this keyword is bound to different values based on the context in which it is called.Ok.
In arrow functions,
this is lexically bound, which means it closes overthis from the scope in which the arrow function was defined (parent-scope), and does not change no matter where and how it is invoked / called.Ok.
限制的方法,在一个箭头的函数对象 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 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] |
在的情况下,采用常规法
在一个箭头的优势函数的正则函数法(S)的一个对象,但只有当预计是固定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /* 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] |
在的情况下定义的方法是在
我们可以很容易地使用,使
1 2 3 4 5 6 7 8 9 10 | 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] |
然而,在箭头的函数和不方便吃的错误倾向是异步回调的情况,我们知道在哪里
限制:这需要在箭头的函数的变化,在invocations
我们需要它的时候,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /* 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] |
上面的任何一个函数都不能与箭头函数
这些都是人为的例子,但让我们考虑一些更现实的例子。如果我们必须编写类似于在
因此,不能将
同样,当框架或系统接受一个回调函数以后用动态上下文
1 2 3 4 5 6 7 8 9 10 11 | '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'); }); |
号
这也是Angular2+和Vue.js等框架希望模板组件绑定方法是常规函数/方法的原因,因为它们的调用由绑定函数的框架管理。(Angular使用zone.js管理异步上下文以调用视图模板绑定函数)。好的。
另一方面,在react中,当我们希望将组件的方法作为事件处理程序(例如
这篇文章也可以在我的媒体出版物上浏览。如果你喜欢这部作品,或者有什么意见和建议,请拍手或在媒体上留言。好的。好啊。
简单来说,
1 2 | var a =20; function a(){this.a=10; console.log(a);} //20, since the context here is window. |
。
另一个实例:
1 2 3 4 5 6 7 8 9 | 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个。< BR>
原因是,每当执行一个函数时,都会创建它自己的堆栈,在本例中,
因此,如果我们希望
箭头解决了这个问题,他们不使用
因此,在所有绑定是显式箭头的情况下,都可以通过默认值来解决问题。
我仍然支持我在这条线索的第一个答案中所写的一切。然而,从那时起,我对代码样式的看法就发展起来了,所以我对这个问题有了一个新的答案,它建立在我上一个问题的基础上。
关于词汇
在我最后的回答中,我故意回避了我对这门语言持有的一种潜在的信念,因为它与我所提出的论点没有直接关系。尽管如此,如果没有明确说明这一点,我可以理解为什么许多人在我建议不使用箭头时,会拒绝使用箭头,因为他们发现箭头非常有用。
我的信念是:我们不应该首先使用
我认为这两种情况都不会发生在某些人身上,但即使是对他们来说,他们也一定会发现自己在代码库中工作,在代码库中,每个文件中的
即使使用带箭头的
所有这些都是以下实现的序言:如果不使用
关于简明扼要的语法
当我写第一个答案的时候,我认为即使是对最佳实践的盲目坚持也是值得付出的代价,如果这意味着我可以生成更完美的代码的话。但我最终意识到,简洁也可以作为一种抽象形式来提高代码质量,这足以证明有时偏离最佳实践是合理的。
换句话说:该死,我也想要一个线性函数!
关于指导方针
鉴于
- 不要使用
this 。 - 将函数声明用于按名称调用的函数(因为它们被提升)。
- 对回调使用箭头函数(因为它们往往比较简洁)。