深入理解JavaScript中的箭头函数
从一开始箭头就是JavaScript的一部分,在第一个JavaScript中就建议将内联的脚本代码包裹在HTML的注释中,这可以防止那些不支持JavaScript的浏览器错误滴将你的代码显示为明文。你也许写过下面这样的代码:
<scriptlanguage="javascript"> <!-- document.bgColor="brown";//red //--> </script> <scriptlanguage="javascript"> <!-- document.bgColor="brown";//red //--> </script>
古老的浏览器将看到两个不被支持的标签和一段注释,只有支持JavaScript的新浏览器才会将其解析为JavaScript代码。
为了支持这个古怪的特性,浏览器的JavaScript引擎把<!--作为一个单行注释的开始,这不是开玩笑的,这一直都是这门语言的一部分,并且至今还能用,不仅仅在<script>标签内的首行,而是在JavaScript代码的任何部位都可用,它甚至还能在Node中使用。
凑巧的是,这种风格的注释在ES6中首次被标准化。但这并不是我们将谈论的箭头。
-->也表示一个单行注释,与HTML不同的是,在HTML中,-->之前的部分是注释内容,而在JavaScript中,在-->之后的行才是注释。
只有当-->出现在一行的开始时,才表示该箭头是一个注释,因为在其他情况下,-->是一个操作符(goesto)。
functioncountdown(n){ while(n-->0)//"ngoestozero" alert(n); blastoff(); } functioncountdown(n){ while(n-->0)//"ngoestozero" alert(n); blastoff(); }
上面代码是真实能运行的。循环运行直到n为0,这并不是ES6的新特性,但结合我们熟悉的特性,这具有很强的误导性。你能搞明白上面代码的运行情况吗?你可以在StackOverflow上找到相应的解答。
当然还有一个箭头,那就是小于等于操作符<=,也许你还可以找到使用箭头的地方,但我们还是停下来,看一个我们从没见过的箭头:
- <!--单行注释
- -->goesto操作符
- <=小于等于操作符
- =>???
那么,=>表示什么呢?这就是本文将讨论的话题。
首先,我们来谈谈函数。
无处不在的函数表达式
JavaScript一个有趣的特点是,任何时候你需要一个函数,你可以很方便地创建它们。
例如,为一个按钮绑定点击事件:
$("#confetti-btn").click( $("#confetti-btn").click(
jQuery的.click()方法需要一个函数作为参数,我们可以很方便地就地创建一个函数:
$("#confetti-btn").click(function(event){ playTrumpet(); fireConfettiCannon(); }); $("#confetti-btn").click(function(event){ playTrumpet(); fireConfettiCannon(); });
现在对我们来说,编写这样的代码是最自然的事了。但是在JavaScript流行起来之前,这种风格的代码看起来还是有些奇怪,因为在其他语言中都没有这样的特性。在1958年,Lisp就有了函数表达式,也叫lambda函数,而在存在多年的C++、Python、C#和Java中没有该特性。
现在,这四门语言都有了lambda表达式,而且新出现的语言都普遍内置了lambda表达式。如今JavaScript也支持该特性了,这必须感谢那些重度依赖lambda表达式的库的开发者,这推动了该特性被广泛采纳。
与其他几门语言相比,JavaScript的语法略显冗长:
//Averysimplefunctioninsixlanguages. function(a){returna>0;}//JS [](inta){returna>0;}//C++ (lambda(a)(>a0));;Lisp lambdaa:a>0#Python a=>a>0//C# a->a>0//Java //Averysimplefunctioninsixlanguages. function(a){returna>0;}//JS [](inta){returna>0;}//C++ (lambda(a)(>a0));;Lisp lambdaa:a>0#Python a=>a>0//C# a->a>0//Java
箭头函数
ES6引入了一种新的语法来编写函数:
//ES5 varselected=allJobs.filter(function(job){ returnjob.isSelected(); }); //ES6 varselected=allJobs.filter(job=>job.isSelected()); //ES5 varselected=allJobs.filter(function(job){ returnjob.isSelected(); }); //ES6 varselected=allJobs.filter(job=>job.isSelected());
当你需要只有一个参数的函数,箭头函数的语法可以简化为Identifier=>Expression,直接省略了function和return关键字,连括号和结尾的分号也同时省略了。
编写一个有多个(或没有参数,或Rest参数和参数默认值,或解构参数)参数的函数,你需要用括号将参数括起来:
//ES5 vartotal=values.reduce(function(a,b){ returna+b; },0); //ES6 vartotal=values.reduce((a,b)=>a+b,0); //ES5 vartotal=values.reduce(function(a,b){ returna+b; },0); //ES6 vartotal=values.reduce((a,b)=>a+b,0);
箭头函数还可以与一些工具函数库完美地配合使用,比如Underscore.js和Immutable,事实上,Immutable文档中的例子全部都是使用ES6编写,其中有很多已经使用到了箭头函数。
函数体除了使用一个表达式外,箭头函数还可以包含一个语句块,回忆之前我们提到过的例子:
//ES5 $("#confetti-btn").click(function(event){ playTrumpet(); fireConfettiCannon(); }); //ES5 $("#confetti-btn").click(function(event){ playTrumpet(); fireConfettiCannon(); });
下面是采用箭头函数的写法:
//ES6 $("#confetti-btn").click(event=>{ playTrumpet(); fireConfettiCannon(); }); //ES6 $("#confetti-btn").click(event=>{ playTrumpet(); fireConfettiCannon(); });
需要注意的是,使用语句块的箭头函数不会自动返回一个值,必须显式地使用return来返回一个值。
还有一个忠告,当使用箭头函数来返回一个对象时,始终使用括号将返回的对象括起来:
//createanewemptyobjectforeachpuppytoplaywith varchewToys=puppies.map(puppy=>{});//BUG! varchewToys=puppies.map(puppy=>({}));//ok //createanewemptyobjectforeachpuppytoplaywith varchewToys=puppies.map(puppy=>{});//BUG! varchewToys=puppies.map(puppy=>({}));//ok
因为空对象{}与空语句块{}看上去一模一样,ES6将始终把紧跟在=>后面的{当作语句块的开始,而不是一个对象的开始,那么puppy=>{}就被解析为一个没有函数体的箭头函数,而且返回值为undefined。