ES6 in Depth: Arrow Funtions
箭头是JS每个开始的一部分,JS的第一个指导说可以在 HTML 注释中内置脚本代码,这会导致浏览器错误地认为JS代码是文本,从而不能运行JS。你可能会编写代码如下:
1 2 3 4 5 |
|
旧的浏览器只会看到两个不支持的标签和一个注释,只有新的浏览器会看到 JS 代码。
为了支持这奇怪的代码,你浏览器中的 JS 引擎会将 <!--
认为是单行注释的开始。没有开玩笑,这至始至终是JS语言的一部分,而且至今仍然是可用的。这不仅仅可以在内置的 <script>
的顶部,还可以在 JS 的任何地方,它甚至在 Node 也是能工作的。
由此,ES6 的注释风格第一次的标准化,但这并不是我们这里所谈到的箭头。
序列箭头-->
也表示一个单行的注释,奇怪的是,HTML认为在-->
之前的部分是注释,而JS认为-->
之后剩余部分是注释。
这确实很怪异。这箭头只有当其出现在行的开头时才会认为是注释。这是因为在其它的内容中,-->
是一个JS的操作符,“去到”操作符。
1 2 3 4 5 |
|
这代码确实可以运行链接, 这个循环运行会从 n 到 0 。这也不是ES6的一个新特性,它其实是类似箭头的操作组合,这确实会有些误导。你能猜出指出是什么吗?和平常一样,这答案的迷云可以在 Stack Overflow 中得到。
好了,<=
表示小于等于操作符,你可以找出JS代码中更多的这些箭头。细想一下有哪些箭头,但是现在先暂停一下,观察一下一个不认识的箭头:
1 2 3 4 |
|
=>
会发现什么呢?今天,让我们研究一下。
首先,让我们稍微讨论一下 函数。
无处不在的函数表达式
JS中一个有趣的特性是可以在任何时候添加你所需要的函数,你只需要在运行代码的中部编写合适的函数。
例如,你可以试图告诉浏览器当用户点击具体的按钮时做什么,你会开始编写:
1
|
|
jQuery 的 .click()
方法需要一个参数:一个函数。没问题,你仅需要编写合适的函数,如下:
1 2 3 4 |
|
现在的我们已经很自然地编写出这类似的代码了,细想一下,这写法在JS流行之前是显得奇怪的,很多的编程语言并不会有这一特性。在1958年,Lisp 有其函数表达式,它叫 lambda 函数。但是,C++、Python、C# 和 Java 没有函数也生存了很多年。
这情况不会再有了,这四种语言现在也有 lambda 函数。更新的语言普遍都会内置有 lambda 函数。我们 JS 也很感谢 —- 早期 JS 编程人员大胆地构建大量的 lambda 库,从而使得这特性广泛地被使用。
稍微有些伤心的是,在我提到过的所有编程语言中,JS的lambda语法证明是最为冗长的。
1 2 3 4 5 6 7 |
|
你箭袋中的新箭头
ES6 介绍编写函数的新的语法:
1 2 3 4 5 6 7 |
|
当你仅是需要个简单的一个参数的函数,这新的箭头函数语法只需要简单地 Identifier => Expression
。你可以不用编写 function
和return
这些关键字,同时包括 括号、大括号、分号。
(我个人是非常喜欢这个特性的,没有限定键入function
对我来说十分的重要,因为我时不时会键入functoin
,从而不得不回头去更正它。)
为了编写多参数的函数(或者没有参数、剩余和默认参数、或解构参数),你将需要添加括号和参数列表。
1 2 3 4 5 6 7 |
|
我认为它看起来是十分好的。
箭头函数可以像某些库中的功能性工具(functional tool)一样漂亮地工作,如 Underscore.js 和 Immutable。实际上,Immutable的文档 中的例子都是用ES6来编写的,所以其中的一些已经使用上了 箭头函数。
那么,对于非功能性的设置该怎么办呢?箭头函数 可以包含着一个代码块,而不仅是一个表达式。回顾一下我们之前的例子:
1 2 3 4 5 |
|
在ES6中如下:
1 2 3 4 5 |
|
一个小提升,在使用 Promises 时,这作用会更加有魔法,它可以将类如 }).then( function (result) {
的行堆起来。
注意,代码块的箭头函数并不会自动返回一个值,所以需要 return
进行返回值操作。
当使用箭头函数来创建普通对象时,需要用括号将对象括起来:
1 2 3 |
|
不幸的是,一个空对象和一个空的代码块实际上是一样的。在 ES6 中,箭头后面的 {
会总是被认为是 代码块的开始标识,而不是对象的开始。因此,代码 puppy => {}
会被编译为一个箭头函数,并不会做什么,返回值为 undefined
。
更多的困扰,一个对象从字面中如 {key: value}
至少看起来像一包含了 标签语句的代码块,但这要看你的JS引擎怎么识别它。幸运的是,{
是唯一的歧义的字符,所以使用括号来包含对象 是 唯一 你需要谨记的怪点。
this
是什么
普通的 function
函数和箭头函数有些微小的差别。箭头函数没有它们自有的 this
的值。箭头函数中的 this
总是来源于包含此箭头函数的作用域。
在我们试图在实践中指出上面意味着什么之前,让我们稍微后退一下。
JS 中的 this
是怎么工作的?它的值来源于哪里?这并没有简短的答案。如果你头脑认为它很简单,是因为你已经处理它很长时间了。
引起 this
这提问的原因是 function
函数会自动接收一个 this
的值,无论他们是否想要它。你是否编写过这黑代码:
1 2 3 4 5 6 7 8 9 10 |
|
这里,你可能会喜欢在内部的函数中编写 this.add(piece)
。但是不幸,内部函数并不会继承外部函数的this
值。在内部函数中,this
将可能会是window
或者undefined
。临时变量self
作用是将外部函数中的this
传递到内部函数中。(另外的方法是在内部使用 .bind(this)
,但是两种方法都不是很漂亮。)
在 ES6 中,如果你按照以下规则使用,这种this
的黑代码将会消失:
- 当调用非箭头函数的方法时,使用
object.method()
语法进行调用,如此这函数会从调用者中接收到一个有意义的this
的值。 - 在任何地方都使用箭头函数
1 2 3 4 5 6 7 8 |
|
通过 方法箭头,我不再会编写 functoin
了,这真是个很好的想法。
箭头和非箭头函数还有一个细小的差别:箭头函数并不会获取他们自己的 arguments
对象。同时,在ES6中,你最好在任何的地方都使用剩余和默认参数。
使用箭头可穿透黑科技的核心
我们已经谈到很多箭头函数的实践应用,我还想提到一个可能会用到的点:把 ES6 中的箭头函数作为一个学习的工具,去挖掘计算领域深层次的东西。无论其是否实用,这都取决于你自己。
在1936年,Alonzo Church 和 Alan Turing 各自独立开发了强大的的数学计算模型。Turing 称之为 a-machines
,但是大家从开始都称为 图灵机器(Turing machines)。Church 编写的是想取代函数,他的模型称为 λ-calculus。 (λ 是希腊语中的lambda的小写。) 这就是Lisp使用关键字 LAMBDA
来声明函数的原因,这也是为什么我们今天称函数为 “lambda”的原因。
但是,什么是 λ-calculus ? 计算模型(model of computation) 表示什么呢?
简短的文字是很难解释的,但是这里是我的理解:λ-calculus 是第一种编程语言之一。虽然它并不是被设计为一个编程语言,随后的一到二十年存储程序的计算机也随之出现。但是,它作为一个非常简单、纯粹的数学语言,可以展示出你想要的各种计算。Church 希望这个模型能广泛地用来证明计算。
然后,他发现仅需要在他的系统中有:函数(functions)。
想象一下这种声明是怎么特别的:没有对象、数组,没有数值,没有if
语句、while
循环,没有分号、分配符号、逻辑操作符,或者任意的循环,白手起家,只使用函数能够做到 JS 做到的任何类型的计算操作。
下面是个数学算法的程序,使用了 Church λ 的符号:
1
|
|
它等价于JS中的
1 2 |
|
如此,JS就包含了 λ-calculus (lambda积分)的实现,并且是能够运行。The λ-calculus is in JavaScript
关于 Alonzo Church 和之后的研究人员对 λ-calculus 做什么的故事,还有其是怎么潜移默化地影响着每个主流编程语言,这已经超出了这篇文章讨论的范围了。但是,如果你对基础计算科感兴趣,或者你仅仅是想看到一编程语言没有其它、只有函数是怎么做到循环和递归的,你可能要努力并花费些下雨的下午去查看 Church numerals 和 fixed-point combinators, 然后将代码运行于 Firefox 的控制台或者 Scratchpad 。由于 ES6 的箭头 拥有其它力量,JS可以光明地声称它是探索 λ-calculus 的最好编程语言。
什么时候使用箭头(箭头函数)
ES6 中的箭头函数在Firefox是由我还实现的,于2013年。Jan de Mooij 的工作是其更为快速,同时感谢 Tooru Fujisawa 和 ziyunfei 的补丁。
箭头函数也在微软的Edge浏览器的预览版本中支持了,它们也可以在 Babel , Traceur 和 TypeScript 中使用了,如此你现在可以使用它们(从而使用箭头函数)。
我们的下个主题是ES6中比较特殊的一个特性。我们可以看到typeof x
会返回一个完成新的值。那么我们会问:什么时候返回的不是string?我们将会努力寻找其相等的含义,确实有些怪异。那么,请于下周加入我们,继续深入 ES6 吧。