本文為原創(chuàng),作者為Mozilla Web、Jason Orendorff中狂,譯者為L(zhǎng)enville。未經(jīng)許可扑毡,拒絕任何形式的轉(zhuǎn)載胃榕。
ECMAScript 2016的發(fā)布只帶來(lái)了Array.prototype.includes和冪運(yùn)算符兩個(gè)新特性(http://www.ecma-international.org/ecma-262/7.0/index.html)。而ECMAScript 6的發(fā)布瞄摊,卻給Javascript帶了了重大變革勋又。ES6中包含的許多新的語(yǔ)言特性,使JS變得更加強(qiáng)大换帜,更富表現(xiàn)力楔壤。
- ECMAScript以及ES6的地位
編程語(yǔ)言JavaScript是ECMAScript的實(shí)現(xiàn)和擴(kuò)展,由ECMA(一個(gè)類(lèi)似W3C的標(biāo)準(zhǔn)組織)參與進(jìn)行標(biāo)準(zhǔn)化惯驼。ECMAScript定義了:
語(yǔ)言語(yǔ)法 – 語(yǔ)法解析規(guī)則蹲嚣、關(guān)鍵字、語(yǔ)句跳座、聲明端铛、運(yùn)算符等泣矛。
類(lèi)型 – 布爾型疲眷、數(shù)字、字符串您朽、對(duì)象等狂丝。
原型和繼承
內(nèi)建對(duì)象和函數(shù)的標(biāo)準(zhǔn)庫(kù) – JSON换淆、Math、數(shù)組方法几颜、對(duì)象自省方法等倍试。
ECMAScript標(biāo)準(zhǔn)不定義HTML或CSS的相關(guān)功能,也不定義類(lèi)似DOM(文檔對(duì)象模型)的Web API蛋哭,這些都在獨(dú)立的標(biāo)準(zhǔn)中進(jìn)行定義县习。ECMAScript涵蓋了各種環(huán)境中JS的使用場(chǎng)景,無(wú)論是瀏覽器環(huán)境還是類(lèi)似node.js的非瀏覽器環(huán)境谆趾。
ES6:重大的版本升級(jí)
早在2009年躁愿,ES5剛剛發(fā)布,自那時(shí)起沪蓬,ES標(biāo)準(zhǔn)委員會(huì)一直在緊鑼密鼓地籌備新的JS語(yǔ)言標(biāo)準(zhǔn)——ES6彤钟。
ES6是一次重大的版本升級(jí),與此同時(shí)跷叉,由于ES6秉承著最大化兼容已有代碼的設(shè)計(jì)理念逸雹,你過(guò)去編寫(xiě)的JS代碼將繼續(xù)正常運(yùn)行。事實(shí)上云挟,許多瀏覽器已經(jīng)支持部分ES6特性梆砸,并將繼續(xù)努力實(shí)現(xiàn)其余特性。這意味著植锉,在一些已經(jīng)實(shí)現(xiàn)部分特性的瀏覽器中辫樱,你的JS代碼已經(jīng)可以正常運(yùn)行。如果到目前為止你尚未遇到任何兼容性問(wèn)題俊庇,那么你很有可能將不會(huì)遇到這些問(wèn)題狮暑,瀏覽器正飛速實(shí)現(xiàn)各種新特性。
版本號(hào)6
ECMAScript標(biāo)準(zhǔn)的歷史版本分別是1辉饱、2搬男、3、5彭沼。
那么為什么沒(méi)有第4版缔逛?其實(shí),在過(guò)去確實(shí)曾計(jì)劃發(fā)布提出巨量新特性的第4版姓惑,但最終卻因想法太過(guò)激進(jìn)而慘遭廢除(這一版標(biāo)準(zhǔn)中曾經(jīng)有一個(gè)極其復(fù)雜的支持泛型和類(lèi)型推斷的內(nèi)建靜態(tài)類(lèi)型系統(tǒng))褐奴。
ES4飽受爭(zhēng)議,當(dāng)標(biāo)準(zhǔn)委員會(huì)最終停止開(kāi)發(fā)ES4時(shí)于毙,其成員同意發(fā)布一個(gè)相對(duì)謙和的ES5版本敦冬,隨后繼續(xù)制定一些更具實(shí)質(zhì)性的新特性。這一明確的協(xié)商協(xié)議最終命名為“Harmony”唯沮,因此脖旱,ES5規(guī)范中包含這樣兩句話(huà):
ECMAScript是一門(mén)充滿(mǎn)活力的語(yǔ)言堪遂,并在不斷進(jìn)化中。
未來(lái)版本的規(guī)范中將持續(xù)進(jìn)行重要的技術(shù)改進(jìn)萌庆。
這一聲明許下了一個(gè)未來(lái)的承諾溶褪。
兌現(xiàn)承諾
2009年發(fā)布的改進(jìn)版本ES5,引入了Object.create()(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create)践险、Object.defineProperty()猿妈、getters和setters、嚴(yán)格模式以及JSON對(duì)象巍虫。我已經(jīng)使用過(guò)所有這些新特性于游,并且我非常喜歡ES5做出的改進(jìn)。但事實(shí)上垫言,這些改進(jìn)并沒(méi)有深入影響我編寫(xiě)JS代碼的方式贰剥,對(duì)我來(lái)說(shuō)最大的革新大概就是新的數(shù)組方法:.map()、. filter()這些筷频。
但是蚌成,ES6并非如此!經(jīng)過(guò)持續(xù)幾年的磨礪凛捏,它已成為JS有史以來(lái)最實(shí)質(zhì)的升級(jí)担忧,新的語(yǔ)言和庫(kù)特性就像無(wú)主之寶,等待有識(shí)之士的發(fā)掘坯癣。新的語(yǔ)言特性涵蓋范圍甚廣瓶盛,小到受歡迎的語(yǔ)法糖,例如箭頭函數(shù)(arrow functions)和簡(jiǎn)單的字符串插值(string interpolation)惩猫,大到燒腦的新概念,例如代理(proxies)和生成器(generators)。
- ES6徹底改變了編寫(xiě)JS代碼的方式
ES6徹底改變了我們編寫(xiě)JS代碼的方式,讓我們就從經(jīng)典的循環(huán)開(kāi)始說(shuō)起。
之前我們是怎么寫(xiě)循環(huán)的
我們將從一個(gè)經(jīng)典的“遺漏特性”說(shuō)起,十年來(lái)我一直期待在JavaScript中看到的它梨水。所以從現(xiàn)在起就加入我們吧,一起領(lǐng)略一下ES6迭代器(iterators)和新的for-of循環(huán)!
我們?nèi)绾伪闅v數(shù)組中的元素?20年前JavaScript剛萌生時(shí),你可能這樣實(shí)現(xiàn)數(shù)組遍歷:
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
自ES5正式發(fā)布后,你可以使用內(nèi)建的forEach(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)方法來(lái)遍歷數(shù)組:
myArray.forEach(function (value) {
console.log(value);
});
這段代碼看起來(lái)更加簡(jiǎn)潔列敲,但這種方法也有一個(gè)小缺陷:你不能使用break語(yǔ)句中斷循環(huán),也不能使用return語(yǔ)句返回到外層函數(shù)麦萤。
當(dāng)然姻檀,如果只用for循環(huán)的語(yǔ)法來(lái)遍歷數(shù)組元素也很不錯(cuò)歼疮。
那么铸磅,你一定想嘗試一下for-in循環(huán):
for (var index in myArray) { // 千萬(wàn)別這樣做
console.log(myArray[index]);
}
這絕對(duì)是一個(gè)糟糕的選擇,為什么呢?
在這段代碼中,賦給index的值不是實(shí)際的數(shù)字报辱,而是字符串“0”、“1”、“2”漂辐,此時(shí)很可能在無(wú)意之間進(jìn)行字符串算數(shù)計(jì)算,例如:“2” + 1 == “21”摘仅,這給編碼過(guò)程帶來(lái)極大的不便。
作用于數(shù)組的for-in循環(huán)體除了遍歷數(shù)組元素外按声,還會(huì)遍歷自定義屬性(https://developer.mozilla.org/en-US/docs/Glossary/Expando)膳犹。舉個(gè)例子,如果你的數(shù)組中有一個(gè)可枚舉屬性myArray.name签则,循環(huán)將額外執(zhí)行一次须床,遍歷到名為“name”的索引。就連數(shù)組原型鏈上的屬性都能被訪(fǎng)問(wèn)到渐裂。
最讓人震驚的是豺旬,在某些情況下,這段代碼可能按照隨機(jī)順序遍歷數(shù)組元素柒凉。
簡(jiǎn)而言之族阅,for-in是為普通對(duì)象設(shè)計(jì)的,你可以遍歷得到字符串類(lèi)型的鍵膝捞,因此不適用于數(shù)組遍歷坦刀。
強(qiáng)大的for-of循環(huán)
還記得前面我向你們承諾過(guò)的話(huà)么?ES6不會(huì)破壞你已經(jīng)寫(xiě)好的JS代碼蔬咬。目前看來(lái)鲤遥,成千上萬(wàn)的Web網(wǎng)站依賴(lài)for-in循環(huán),其中一些網(wǎng)站甚至將其用于數(shù)組遍歷林艘。如果想通過(guò)修正for-in循環(huán)增加數(shù)組遍歷支持會(huì)讓這一切變得更加混亂盖奈,因此,標(biāo)準(zhǔn)委員會(huì)在ES6中增加了一種新的循環(huán)語(yǔ)法來(lái)解決目前的問(wèn)題狐援。
就像這樣:
for (var value of myArray) {
console.log(value);
}
是的钢坦,與之前的內(nèi)建方法相比,這種循環(huán)方式看起來(lái)是否有些眼熟啥酱?那好场钉,我們將要探究一下for-of循環(huán)的外表下隱藏著哪些強(qiáng)大的功能。現(xiàn)在懈涛,只需記坠渫颉:
這是最簡(jiǎn)潔、最直接的遍歷數(shù)組元素的語(yǔ)法
這個(gè)方法避開(kāi)了for-in循環(huán)的所有缺陷
與forEach()不同的是,它可以正確響應(yīng)break宇植、continue和return語(yǔ)句 for-in循環(huán)用來(lái)遍歷對(duì)象屬性得封。
for-of循環(huán)用來(lái)遍歷數(shù)據(jù)—例如數(shù)組中的值。
但是指郁,不僅如此忙上!
for-of循環(huán)也可以遍歷其它的集合
for-of循環(huán)不僅支持?jǐn)?shù)組,還支持大多數(shù)類(lèi)數(shù)組對(duì)象闲坎,例如DOM NodeList對(duì)象(https://developer.mozilla.org/en-US/docs/Web/API/NodeList)疫粥。
for-of循環(huán)也支持字符串遍歷,它將字符串視為一系列的Unicode字符來(lái)進(jìn)行遍歷:
for (var chr of "") {
alert(chr);
}
它同樣支持Map和Set對(duì)象遍歷腰懂。
對(duì)不起梗逮,你一定沒(méi)聽(tīng)說(shuō)過(guò)Map和Set對(duì)象。他們是ES6中新增的類(lèi)型绣溜。我們將在后續(xù)的文章講解這兩個(gè)新的類(lèi)型慷彤。如果你曾在其它語(yǔ)言中使用過(guò)Map和Set,你會(huì)發(fā)現(xiàn)ES6中的并無(wú)太大出入怖喻。
舉個(gè)例子底哗,Set對(duì)象可以自動(dòng)排除重復(fù)項(xiàng):
// 基于單詞數(shù)組創(chuàng)建一個(gè)set對(duì)象
var uniqueWords = new Set(words);
生成Set對(duì)象后,你可以輕松遍歷它所包含的內(nèi)容:
for (var word of uniqueWords) {
console.log(word);
}
Map對(duì)象稍有不同:內(nèi)含的數(shù)據(jù)由鍵值對(duì)組成锚沸,所以你需要使用解構(gòu)(destructuring)來(lái)將鍵值對(duì)拆解為兩個(gè)獨(dú)立的變量:
for (var [key, value] of phoneBookMap) {
console.log(key + "'s phone number is: " + value);
}
解構(gòu)也是ES6的新特性跋选,我們將在另一篇文章中講解』冢看來(lái)我應(yīng)該記錄這些優(yōu)秀的主題野建,未來(lái)有太多的新內(nèi)容需要一一剖析。
現(xiàn)在恬叹,你只需記缀蛏:未來(lái)的JS可以使用一些新型的集合類(lèi),甚至?xí)懈嗟念?lèi)型陸續(xù)誕生绽昼,而for-of就是為遍歷所有這些集合特別設(shè)計(jì)的循環(huán)語(yǔ)句唯鸭。
for-of循環(huán)不支持普通對(duì)象,但如果你想迭代一個(gè)對(duì)象的屬性硅确,你可以用for-in循環(huán)(這也是它的本職工作)或內(nèi)建的Object.keys()方法:
// 向控制臺(tái)輸出對(duì)象的可枚舉屬性
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}
深入理解
“能工摹形目溉,巧匠竊意×馀”——巴勃羅·畢卡索
ES6始終堅(jiān)持這樣的宗旨:凡是新加入的特性缭付,勢(shì)必已在其它語(yǔ)言中得到強(qiáng)有力的實(shí)用性證明。
舉個(gè)例子循未,新加入的for-of循環(huán)像極了C++陷猫、Java、C#以及Python中的循環(huán)語(yǔ)句。與它們一樣绣檬,這里的for-of循環(huán)支持語(yǔ)言和標(biāo)準(zhǔn)庫(kù)中提供的幾種不同的數(shù)據(jù)結(jié)構(gòu)足陨。它同樣也是這門(mén)語(yǔ)言中的一個(gè)擴(kuò)展點(diǎn)(譯注:關(guān)于擴(kuò)展點(diǎn),建議參考 1. 淺析擴(kuò)展點(diǎn)http://www.blogjava.net/yangbutao/archive/2007/09/27/148500.html 2. What are extensions and extension points?https://wiki.eclipse.org/FAQWhatareextensionsandextensionpoints%3F)娇未。
正如其它語(yǔ)言中的for/foreach語(yǔ)句一樣墨缘,for-of循環(huán)語(yǔ)句通過(guò)方法調(diào)用來(lái)遍歷各種集合。數(shù)組零抬、Maps對(duì)象镊讼、Sets對(duì)象以及其它在我們討論的對(duì)象有一個(gè)共同點(diǎn),它們都有一個(gè)迭代器方法平夜。
你可以給任意類(lèi)型的對(duì)象添加迭代器方法蝶棋。
當(dāng)你為對(duì)象添加myObject.toString()方法后,就可以將對(duì)象轉(zhuǎn)化為字符串褥芒,同樣地嚼松,當(dāng)你向任意對(duì)象添加myObjectSymbol.iterator方法嫡良,就可以遍歷這個(gè)對(duì)象了锰扶。
舉個(gè)例子,假設(shè)你正在使用jQuery寝受,盡管你非常鐘情于里面的.each()方法坷牛,但你還是想讓jQuery對(duì)象也支持for-of循環(huán),你可以這樣做:
// 因?yàn)閖Query對(duì)象與數(shù)組相似
// 可以為其添加與數(shù)組一致的迭代器方法
jQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
好的很澄,我知道你在想什么京闰,那個(gè)[Symbol.iterator]語(yǔ)法看起來(lái)很奇怪,這段代碼到底做了什么呢甩苛?這里通過(guò)Symbol處理了一下方法的名稱(chēng)蹂楣。標(biāo)準(zhǔn)委員會(huì)可以把這個(gè)方法命名為.iterator()方法,但是如果你的代碼中的對(duì)象可能也有一些.iterator()方法讯蒲,這一定會(huì)讓你感到非常困惑痊土。于是在ES6標(biāo)準(zhǔn)中使用symbol來(lái)作為方法名,而不是使用字符串墨林。
你大概也猜到了赁酝,Symbols是ES6中的新類(lèi)型,我們會(huì)在后續(xù)的文章中講解⌒竦龋現(xiàn)在酌呆,你需要記住,基于新標(biāo)準(zhǔn)搔耕,你可以定義一個(gè)全新的symbol隙袁,就像Symbol.iterator,如此一來(lái)可以保證不與任何已有代碼產(chǎn)生沖突。這樣做的代價(jià)是藤乙,這段代碼的語(yǔ)法看起來(lái)會(huì)略顯生硬猜揪,但是這微乎其微代價(jià)卻可以為你帶來(lái)如此多的新特性和新功能,并且你所做的這一切可以完美地向后兼容坛梁。
所有擁有Symbol.iterator的對(duì)象被稱(chēng)為可迭代的而姐。在接下來(lái)的文章中你會(huì)發(fā)現(xiàn),可迭代對(duì)象的概念幾乎貫穿于整門(mén)語(yǔ)言之中划咐,不僅是for-of循環(huán)拴念,還有Map和Set構(gòu)造函數(shù)、解構(gòu)賦值褐缠,以及新的展開(kāi)操作符政鼠。
迭代器對(duì)象
現(xiàn)在,你將無(wú)須親自從零開(kāi)始實(shí)現(xiàn)一個(gè)對(duì)象迭代器队魏,我們會(huì)在下一篇文章詳細(xì)講解公般。為了幫助你理解本文,我們簡(jiǎn)單了解一下迭代器(如果你跳過(guò)這一章胡桨,你將錯(cuò)過(guò)非常精彩的技術(shù)細(xì)節(jié))官帘。
for-of循環(huán)首先調(diào)用集合的Symbol.iterator方法,緊接著返回一個(gè)新的迭代器對(duì)象昧谊。迭代器對(duì)象可以是任意具有.next()方法的對(duì)象刽虹;for-of循環(huán)將重復(fù)調(diào)用這個(gè)方法,每次循環(huán)調(diào)用一次呢诬。舉個(gè)例子涌哲,這段代碼是我能想出來(lái)的最簡(jiǎn)單的迭代器:
var zeroesForeverIterator = {
[Symbol.iterator]: function () {
return this;
},
next: function () {
return {done: false, value: 0};
}
};
每一次調(diào)用.next()方法,它都返回相同的結(jié)果尚镰,返回給for-of循環(huán)的結(jié)果有兩種可能:(a) 我們尚未完成迭代阀圾;(b) 下一個(gè)值為0。這意味著(value of zeroesForeverIterator) {}將會(huì)是一個(gè)無(wú)限循環(huán)狗唉。當(dāng)然初烘,一般來(lái)說(shuō)迭代器不會(huì)如此簡(jiǎn)單。
這個(gè)迭代器的設(shè)計(jì)敞曹,以及它的.done和.value屬性账月,從表面上看與其它語(yǔ)言中的迭代器不太一樣。在Java中澳迫,迭代器有分離的.hasNext()和.next()方法局齿。在Python中,他們只有一個(gè).next() 方法橄登,當(dāng)沒(méi)有更多值時(shí)拋出StopIteration異常抓歼。但是所有這三種設(shè)計(jì)從根本上講都返回了相同的信息讥此。
迭代器對(duì)象也可以實(shí)現(xiàn)可選的.return()和.throw(exc)方法。如果for-of循環(huán)過(guò)早退出會(huì)調(diào)用.return()方法谣妻,異常萄喳、break語(yǔ)句或return語(yǔ)句均可觸發(fā)過(guò)早退出。如果迭代器需要執(zhí)行一些清潔或釋放資源的操作蹋半,可以在.return()方法中實(shí)現(xiàn)他巨。大多數(shù)迭代器方法無(wú)須實(shí)現(xiàn)這一方法。.throw(exc)方法的使用場(chǎng)景就更特殊了:for-of循環(huán)永遠(yuǎn)不會(huì)調(diào)用它减江。但是我們還是會(huì)在下一篇文章更詳細(xì)地講解它的作用染突。
現(xiàn)在我們已了解所有細(xì)節(jié),可以寫(xiě)一個(gè)簡(jiǎn)單的for-of循環(huán)然后按照下面的方法調(diào)用重寫(xiě)被迭代的對(duì)象辈灼。
首先是for-of循環(huán):
for (VAR of ITERABLE) {
一些語(yǔ)句
}
然后是一個(gè)使用以下方法和少許臨時(shí)變量實(shí)現(xiàn)的與之前大致相當(dāng)?shù)氖纠?
var $iterator = ITERABLESymbol.iterator;
var $result = $iterator.next();
while (!$result.done) {
VAR = $result.value;
一些語(yǔ)句
$result = $iterator.next();
}
這段代碼沒(méi)有展示.return()方法是如何處理的份企,我們可以添加這部分代碼,但我認(rèn)為這對(duì)于我們正在講解的內(nèi)容來(lái)說(shuō)過(guò)于復(fù)雜了巡莹。for-of循環(huán)用起來(lái)很簡(jiǎn)單司志,但是其背后有著非常復(fù)雜的機(jī)制。
- 后記
本文的講解就到這里降宅,但是對(duì)于for-of循環(huán)的使用遠(yuǎn)沒(méi)有結(jié)束骂远。
在ES6中有一種新的對(duì)象與for-of循環(huán)配合使用非常契合,我沒(méi)有提及它因?yàn)樗俏覀兿缕恼碌闹黝}钉鸯,我認(rèn)為這種新特性是ES6種最夢(mèng)幻的地方吧史,如果你尚未在類(lèi)似Python和C#的語(yǔ)言中遇到它邮辽,你一開(kāi)始很可能會(huì)發(fā)現(xiàn)它令人難以置信唠雕,但是這是編寫(xiě)迭代器最簡(jiǎn)單的方式,在重構(gòu)中非常有用吨述,并且它很可能改變我們書(shū)寫(xiě)異步代碼的方式岩睁,無(wú)論是在瀏覽器環(huán)境還是服務(wù)器環(huán)境,所以揣云,在下篇文章中捕儒,請(qǐng)務(wù)必和我一起來(lái)仔細(xì)看看 ES6 的生成器:generators。