箭頭符號在JavaScript誕生時就已經(jīng)存在,當初第一個JavaScript教程曾建議在HTML注釋內(nèi)包裹行內(nèi)腳本噪珊,這樣可以避免不支持JS的瀏覽器誤將JS代碼顯示為文本晌缘。你會寫這樣的代碼:
<scriptlanguage="javascript">
<!--
? ?document.bgColor = "brown";? // red
? ?// -->
</script>
老式瀏覽器會將這段代碼解析為兩個不支持的標簽和一條注釋,只有新式瀏覽器才能識別出其中的JS代碼痢站。
為了支持這種奇怪的hack方式磷箕,瀏覽器中的JavaScript引擎將標簽后的首行,在JS代碼的每個角落你都有可能見到它阵难,甚至在Node中也是如此岳枷。-->
碰巧,這種注釋風格首次在ES6中被標準化了,但在新標準中箭頭被用來做其它事情空繁。
箭頭序列-->同樣是單行注釋的一部分殿衰。古怪的是,在HTML中-->之前的字符是注釋的一部分盛泡,而在JS中-->之后的部分才是注釋闷祥。
你一定感到陌生的是,只有當箭頭在行首時才會注釋當前行傲诵。這是因為在其它上下文中凯砍,-->是一個JS運算符:“趨向于”運算符!
function countdown(n) {
while(n-->0) ? ? // "n goes to zero"
alert(n);
blastoff();
}
上面這段代碼可以正常運行拴竹,循環(huán)會一直重復直到n趨于0悟衩,這當然不是ES6中的新特性,它只不過是將兩個你早已熟悉的特性通過一些誤導性的手段結合在一起栓拜。你能理解么座泳?通常來說,類似這種謎團都可以在Stack Overflow上找到答案菱属。
當然钳榨,同樣地,小于等于操作符<=也形似箭頭纽门,你可以在JS代碼、隱藏的圖片樣式中找到更多類似的箭頭营罢,但是我們就不繼續(xù)尋找了赏陵,你應該注意到我們漏掉了一種特殊的箭頭。
? <!-- 單行注釋
--> “趨向于”操作符
<= 小于等于
=> 這又是什么饲漾?
=> 到底是什么蝙搔?我們今天就來一探究竟。
首先考传,我們談論一些有關函數(shù)的事情吃型。
函數(shù)表達式無處不在
JavaScript中有一個有趣的特性,無論何時僚楞,當你需要一個函數(shù)時勤晚,你都可以在想添加的地方輸入這個函數(shù)。
舉個例子泉褐,假設你嘗試告訴瀏覽器用戶點擊一個特定按鈕后的行為赐写,你會這樣寫:
$("#confetti-btn").click(
jQuery的.click()方法接受一個參數(shù):一個函數(shù)。沒問題膜赃,你可以在這里輸入一個函數(shù):
$("#confetti-btn").click(function(event){playTrumpet();fireConfettiCannon();});
對 于現(xiàn)在的我們來說挺邀,寫出這樣的代碼相當自然,而回憶起在這種編程方式流行之前,這種寫法相對陌生一些端铛,許多語言中都沒有這種特性泣矛。1958年,Lisp首 先支持函數(shù)表達式禾蚕,也支持調用lambda函數(shù)乳蓄,而C++,Python夕膀、C#以及Java在隨后的多年中一直不支持這樣的特性虚倒。
現(xiàn)在截然不同,所有的四種語言都已支持lambda函數(shù)产舞,更新出現(xiàn)的語言普遍都支持內(nèi)建的lambda函數(shù)魂奥。我們必須要感謝JavaScript和早期的JavaScript程序員,他們勇敢地構建了重度依賴lambda函數(shù)的庫易猫,讓這種特性被廣泛接受耻煤。
令人傷感的是,隨后在所有我提及的語言中准颓,只有JavaScript的lambda的語法最終變得冗長乏味哈蝇。
// 六種語言中的簡單函數(shù)示例
function(a){returna>0;}// JS
[](int a){returna>0;}// C++
(lambda(a)(>a0));;Lisp
lambda a:a>0# Python
a=>a>0// C#
a->a>0// Java
箭袋中的新羽
ES6中引入了一種編寫函數(shù)的新語法
// ES5
var selected=allJobs.filter(function(job){
returnjob.isSelected();
});
// ES6
var selected = allJobs.filter(job=>job.isSelected());
當你只需要一個只有一個參數(shù)的簡單函數(shù)時,可以使用新標準中的箭頭函數(shù)攘已,它的語法非常簡單:標識符=>表達式炮赦。你無需輸入function和return,一些小括號样勃、大括號以及分號也可以省略吠勘。
(我個人對于這個特性非常感激,不再需要輸入function這幾個字符對我而言至關重要峡眶,因為我總是不可避免地錯誤寫成functoin剧防,然后我就不得不回過頭改正它。)
如果要寫一個接受多重參數(shù)(也可能沒有參數(shù)辫樱,或者是不定參數(shù)峭拘、默認參數(shù)、參數(shù)解構)的函數(shù)狮暑,你需要用小括號包裹參數(shù)list鸡挠。
// ES5
artotal=values.reduce(function(a,b){
returna+b;
},0);
// ES6
var total=values.reduce((a,b)=>a+b,0);
我認為這看起來酷斃了。
正如你使用類似Underscore.js和Immutable.js這樣的庫提供的函數(shù)工具心例,箭頭函數(shù)運行起來同樣美不可言宵凌。事實上,Immutable的文檔中的示例全都由ES6寫成止后,其中的許多特性已經(jīng)用上了箭頭函數(shù)瞎惫。
那么不是非常函數(shù)化的情況又如何呢溜腐?除表達式外,箭頭函數(shù)還可以包含一個塊語句瓜喇⊥σ妫回想一下我們之前的示例:
// ES5
$("#confetti-btn").click(function(event){
playTrumpet();
fireConfettiCannon();
});
這是它們在ES6中看起來的樣子:
// ES6
$("#confetti-btn").click(event=>{
playTrumpet();
fireConfettiCannon();
});
這是一個微小的改進,對于使用了Promises的代碼來說箭頭函數(shù)的效果可以變得更加戲劇性乘寒,}).then(function (result) {這樣的一行代碼可以堆積起來望众。
注意,使用了塊語句的箭頭函數(shù)不會自動返回值伞辛,你需要使用return語句將所需值返回烂翰。
小提示:當使用箭頭函數(shù)創(chuàng)建普通對象時,你總是需要將對象包裹在小括號里蚤氏。
// 為與你玩耍的每一個小狗創(chuàng)建一個新的空對象
var chewToys=puppies.map(puppy=>{});// 這樣寫會報Bug甘耿!
var chewToys=puppies.map(puppy=>({}));//
用小括號包裹空對象就可以了。
不幸的是竿滨,一個空對象{}和一個空的塊{}看起來完全一樣佳恬。ES6中的規(guī)則是,緊隨箭頭的{被解析為塊的開始于游,而不是對象的開始毁葱。因此,puppy => {}這段代碼就被解析為沒有任何行為并返回undefined的箭頭函數(shù)贰剥。
更令人困惑的是倾剿,你的JavaScript引擎會將類似{key: value}的對象字面量解析為一個包含標記語句的塊。幸運的是鸠澈,{是唯一一個有歧義的字符柱告,所以用小括號包裹對象字面量是唯一一個你需要牢記的小竅門。
這個函數(shù)的this值是什么呢笑陈?
普通function函數(shù)和箭頭函數(shù)的行為有一個微妙的區(qū)別,箭頭函數(shù)沒有它自己的this值葵袭,箭頭函數(shù)內(nèi)的this值繼承自外圍作用域涵妥。
在我們嘗試說明這個問題前,先一起回顧一下坡锡。
JavaScript中的this是如何工作的蓬网?它的值從哪里獲取鹉勒?這些問題的答案可都不簡單帆锋,如果你對此倍感清晰,一定因為你長時間以來一直在處理類似的問題禽额。
這個問題經(jīng)常出現(xiàn)的其中一個原因是锯厢,無論是否需要皮官,function函數(shù)總會自動接收一個this值。你是否寫過這樣的hack代碼:
{
...
addAll:functionaddAll(pieces){
varself=this;
_.each(pieces,function(piece){
self.add(piece);
});
},
...
}
在這里实辑,你希望在內(nèi)層函數(shù)里寫的是this.add(piece)捺氢,不幸的是,內(nèi)層函數(shù)并未從外層函數(shù)繼承this的值剪撬。在內(nèi)層函數(shù)里摄乒,this會是window或undefined,臨時變量self用來將外部的this值導入內(nèi)部函數(shù)残黑。(另一種方式是在內(nèi)部函數(shù)上執(zhí)行.bind(this)馍佑,兩種方法都不甚美觀。)
在ES6中梨水,不需要再hackthis了拭荤,但你需要遵循以下規(guī)則:
通過object.method()語法調用的方法使用非箭頭函數(shù)定義,這些函數(shù)需要從調用者的作用域中獲取一個有意義的this值冰木。
其它情況全都使用箭頭函數(shù)穷劈。
// ES6
{
...
addAll:functionaddAll(pieces){
_.each(pieces,piece=>this.add(piece));
},
...
}
在ES6的版本中,注意addAll方法從它的調用者處獲取了this值踊沸,內(nèi)部函數(shù)是一個箭頭函數(shù)歇终,所以它繼承了外圍作用域的this值。
超贊的是逼龟,在ES6中你可以用更簡潔的方式編寫對象字面量中的方法评凝,所以上面這段代碼可以簡化成:
// ES6的方法語法
{
...
addAll(pieces){
_.each(pieces,piece=>this.add(piece));
},
...
}
在方法和箭頭函數(shù)之間,我再也不會錯寫functoin了腺律,這真是一個絕妙的設計思想奕短!
箭頭函數(shù)與非箭頭函數(shù)間還有一個細微的區(qū)別,箭頭函數(shù)不會獲取它們自己的arguments對象匀钧。誠然翎碑,在ES6中,你可能更多地會使用不定參數(shù)和默認參數(shù)值這些新特性之斯。
借助箭頭函數(shù)洞悉計算機科學的風塵往事
我們已經(jīng)討論了許多箭頭函數(shù)的實際用例琅豆,它還有一種可能的使用方法:將ES6箭頭函數(shù)作為一個學習工具巫击,來深入挖掘計算的本質冻河,是否實用钝计,終將取決于你自己。
1936年瘫絮,Alonzo Church和Alan Turing各自開發(fā)了強大的計算數(shù)學模型涨冀,圖靈將他的模型稱為a-machines,但是每一個人都稱其為圖靈機麦萤。Church寫的是函數(shù)模型鹿鳖,他的模型被稱為lambda演算(λ-calculus)扁眯。這一成果也被Lisp借鑒,用LAMBDA來指示函數(shù)栓辜,這也是為何我們現(xiàn)在將函數(shù)表達式稱為lambda函數(shù)恋拍。
但什么是lambda演算呢?“計算模型”又意味著什么呢藕甩?
用 幾句話解釋清楚很難施敢,但是我會努力闡釋:lambda演算是第一代編程語言的一種形式,但畢竟存儲程序計算機在十幾二十年后才誕生狭莱,所以它原本不是為編程 語言設計的僵娃,而是為了表達任意你想到的計算問題設計的一種極度簡化的純數(shù)學思想的語言。Church希望用這個模型來證明普遍意義的計算腋妙。
最終他發(fā)現(xiàn)默怨,在他的系統(tǒng)中只需要一件東西:函數(shù)。
這種聲明方式無與倫比骤素,不借助對象匙睹、數(shù)組、數(shù)字济竹、if語句痕檬、while循環(huán)、分號送浊、賦值梦谜、邏輯運算符甚或是事件循環(huán),只須使用函數(shù)就可以從0開始重建JavaScript能實現(xiàn)的每一種計算袭景。
這是用Church的lambda標記寫出來的數(shù)學家風格的“程序”示例:
fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))
等效的JavaScript函數(shù)是這樣的:
varfix=f=>(x=>f(v=>x(x)(v)))
? ? ? ? ? ? ? ? (x=>f(v=>x(x)(v)));
所以唁桩,在JavaScript中實現(xiàn)了一個可以運行的lambda演算,它根植于這門語言中耸棒。
Alonzo Church和lambda演算后繼研究者們的故事荒澡,以及它是如何潛移默化地入駐每一門主流編程語言的,已經(jīng)遠超本文的討論范圍与殃。但是如果你對計算機科學 的奠基感興趣仰猖,或者你只是對一門只用函數(shù)就可以做許多類似循環(huán)和遞歸這樣的事情的語言倍感興趣,你可以在一個下雨的午后深入邱奇數(shù)(Church numerals)和不動點組合子(Fixed-point combinator)奈籽,在你的Firefox控制臺或Scratchpad中仔細研究一番。結合ES6的箭頭函數(shù)以及其它強大的功能鸵赫,JavaScript稱得上是一門探索lambda演算的最好的語言衣屏。
我何時可以使用箭頭函數(shù)?
早在2013年辩棒,我就在Firefox中實現(xiàn)了ES6箭頭函數(shù)的功能狼忱,Jan de Mooij為其優(yōu)化加快了執(zhí)行速度膨疏。感謝Tooru Fujisawa以及ziyunfei(譯者注:中國開發(fā)者,為Mozilla作了許多貢獻)后續(xù)打的補丁钻弄。
微軟Edge預覽版中也實現(xiàn)了箭頭函數(shù)的功能佃却,如果你想立即在你的Web項目中使用箭頭函數(shù),可以使用Babel窘俺、Traceur或TypeScript饲帅,這三個工具均已實現(xiàn)相關功能。