本文為前端之巔原創(chuàng)文章脊奋,作者為Jason Orendorff病涨,譯者為Lenville富玷。未經(jīng)許可,拒絕任何形式的轉(zhuǎ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方法來遍歷數(shù)組:
myArray.forEach(function (value) {
console.log(value);
});
這段代碼看起來更加簡潔励两,但這種方法也有一個(gè)小缺陷:你不能使用break語句中斷循環(huán),也不能使用return語句返回到外層函數(shù)囊颅。
當(dāng)然当悔,如果只用for循環(huán)的語法來遍歷數(shù)組元素也很不錯(cuò)。
那么踢代,你一定想嘗試一下for-in循環(huán):
for (var index in myArray) { // 千萬別這樣做
console.log(myArray[index]);
}
這絕對是一個(gè)糟糕的選擇盲憎,為什么呢?
- 在這段代碼中胳挎,賦給index的值不是實(shí)際的數(shù)字饼疙,而是字符串“0”、“1”慕爬、“2”窑眯,此時(shí)很可能在無意之間進(jìn)行字符串算數(shù)計(jì)算,例如:“2” + 1 == “21”医窿,這給編碼過程帶來極大的不便磅甩。
- 作用于數(shù)組的for-in循環(huán)體除了遍歷數(shù)組元素外,還會(huì)遍歷自定義屬性姥卢。舉個(gè)例子卷要,如果你的數(shù)組中有一個(gè)可枚舉屬性myArray.name,循環(huán)將額外執(zhí)行一次隔显,遍歷到名為“name”的索引却妨。就連數(shù)組原型鏈上的屬性都能被訪問到。
- 最讓人震驚的是括眠,在某些情況下彪标,這段代碼可能按照隨機(jī)順序遍歷數(shù)組元素。
- 簡而言之掷豺,for-in是為普通對象設(shè)計(jì)的捞烟,你可以遍歷得到字符串類型的鍵薄声,因此不適用于數(shù)組遍歷。
強(qiáng)大的for-of循環(huán)
還記得在《深入淺出ES6(一):ES6是什么》中我向你們承諾過的話么题画?ES6不會(huì)破壞你已經(jīng)寫好的JS代碼默辨。目前看來,成千上萬的Web網(wǎng)站依賴for-in循環(huán)苍息,其中一些網(wǎng)站甚至將其用于數(shù)組遍歷缩幸。如果想通過修正for-in循環(huán)增加數(shù)組遍歷支持會(huì)讓這一切變得更加混亂,因此竞思,標(biāo)準(zhǔn)委員會(huì)在ES6中增加了一種新的循環(huán)語法來解決目前的問題表谊。
就像這樣:
for (var value of myArray) {
console.log(value);
}
是的,與之前的內(nèi)建方法相比盖喷,這種循環(huán)方式看起來是否有些眼熟爆办?那好,我們將要探究一下for-of循環(huán)的外表下隱藏著哪些強(qiáng)大的功能】问幔現(xiàn)在距辆,只需記住:
- 這是最簡潔暮刃、最直接的遍歷數(shù)組元素的語法
- 這個(gè)方法避開了for-in循環(huán)的所有缺陷
- 與forEach()不同的是跨算,它可以正確響應(yīng)break、continue和return語句
for-in循環(huán)用來遍歷對象屬性沾歪。
for-of循環(huán)用來遍歷數(shù)據(jù)—例如數(shù)組中的值漂彤。
但是雾消,不僅如此灾搏!
for-of循環(huán)也可以遍歷其它的集合
for-of循環(huán)不僅支持?jǐn)?shù)組,還支持大多數(shù)類數(shù)組對象立润,例如DOM NodeList對象狂窑。
for-of循環(huán)也支持字符串遍歷,它將字符串視為一系列的Unicode字符來進(jìn)行遍歷:
for (var chr of "") {
alert(chr);
}
它同樣支持Map和Set對象遍歷桑腮。
對不起泉哈,你一定沒聽說過Map和Set對象。他們是ES6中新增的類型破讨。我們將在后續(xù)的文章講解這兩個(gè)新的類型丛晦。如果你曾在其它語言中使用過Map和Set,你會(huì)發(fā)現(xiàn)ES6中的并無太大出入提陶。
舉個(gè)例子烫沙,Set對象可以自動(dòng)排除重復(fù)項(xiàng):
// 基于單詞數(shù)組創(chuàng)建一個(gè)set對象
var uniqueWords = new Set(words);
生成Set對象后,你可以輕松遍歷它所包含的內(nèi)容:
for (var word of uniqueWords) {
console.log(word);
}
Map對象稍有不同:內(nèi)含的數(shù)據(jù)由鍵值對組成隙笆,所以你需要使用解構(gòu)(destructuring)來將鍵值對拆解為兩個(gè)獨(dú)立的變量:
for (var [key, value] of phoneBookMap) {
console.log(key + "'s phone number is: " + value);
}
解構(gòu)也是ES6的新特性锌蓄,我們將在另一篇文章中講解升筏。看來我應(yīng)該記錄這些優(yōu)秀的主題瘸爽,未來有太多的新內(nèi)容需要一一剖析您访。
現(xiàn)在,你只需記准艟觥:未來的JS可以使用一些新型的集合類灵汪,甚至?xí)懈嗟念愋完懤m(xù)誕生,而for-of就是為遍歷所有這些集合特別設(shè)計(jì)的循環(huán)語句柑潦。
for-of循環(huán)不支持普通對象识虚,但如果你想迭代一個(gè)對象的屬性,你可以用for-in循環(huán)(這也是它的本職工作)或內(nèi)建的Object.keys()方法:
// 向控制臺(tái)輸出對象的可枚舉屬性
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}
深入理解
“能工摹形妒茬,巧匠竊意担锤。”——巴勃羅·畢卡索
ES6始終堅(jiān)持這樣的宗旨:凡是新加入的特性乍钻,勢必已在其它語言中得到強(qiáng)有力的實(shí)用性證明肛循。
舉個(gè)例子,新加入的for-of循環(huán)像極了C++银择、Java多糠、C#以及Python中的循環(huán)語句。與它們一樣浩考,這里的for-of循環(huán)支持語言和標(biāo)準(zhǔn)庫中提供的幾種不同的數(shù)據(jù)結(jié)構(gòu)夹孔。它同樣也是這門語言中的一個(gè)擴(kuò)展點(diǎn)(譯注:關(guān)于擴(kuò)展點(diǎn),建議參考 1. 淺析擴(kuò)展點(diǎn) 2. What are extensions and extension points?)析孽。
正如其它語言中的for/foreach語句一樣搭伤,for-of****循環(huán)語句通過方法調(diào)用來遍歷各種集合。數(shù)組袜瞬、Maps對象怜俐、Sets對象以及其它在我們討論的對象有一個(gè)共同點(diǎn),它們都有一個(gè)迭代器方法邓尤。
你可以給任意類型的對象添加迭代器方法拍鲤。
當(dāng)你為對象添加myObject.toString()方法后,就可以將對象轉(zhuǎn)化為字符串汞扎,同樣地季稳,當(dāng)你向任意對象添加myObjectSymbol.iterator方法,就可以遍歷這個(gè)對象了澈魄。
舉個(gè)例子景鼠,假設(shè)你正在使用jQuery,盡管你非常鐘情于里面的.each()方法一忱,但你還是想讓jQuery對象也支持for-of循環(huán)莲蜘,你可以這樣做:
// 因?yàn)閖Query對象與數(shù)組相似
// 可以為其添加與數(shù)組一致的迭代器方法
jQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
好的谭确,我知道你在想什么,那個(gè)[Symbol.iterator]語法看起來很奇怪票渠,這段代碼到底做了什么呢逐哈?這里通過Symbol處理了一下方法的名稱。標(biāo)準(zhǔn)委員會(huì)可以把這個(gè)方法命名為.iterator()方法问顷,但是如果你的代碼中的對象可能也有一些.iterator()方法昂秃,這一定會(huì)讓你感到非常困惑。于是在ES6標(biāo)準(zhǔn)中使用symbol來作為方法名杜窄,而不是使用字符串肠骆。
你大概也猜到了,Symbols是ES6中的新類型塞耕,我們會(huì)在后續(xù)的文章中講解∈赐龋現(xiàn)在,你需要記住扫外,基于新標(biāo)準(zhǔn)莉钙,你可以定義一個(gè)全新的symbol,就像Symbol.iterator筛谚,如此一來可以保證不與任何已有代碼產(chǎn)生沖突磁玉。這樣做的代價(jià)是,這段代碼的語法看起來會(huì)略顯生硬驾讲,但是這微乎其微代價(jià)卻可以為你帶來如此多的新特性和新功能蚊伞,并且你所做的這一切可以完美地向后兼容。
所有擁有Symbol.iterator的對象被稱為可迭代的吮铭。在接下來的文章中你會(huì)發(fā)現(xiàn)时迫,可迭代對象的概念幾乎貫穿于整門語言之中,不僅是for-of循環(huán)沐兵,還有Map和Set構(gòu)造函數(shù)别垮、解構(gòu)賦值,以及新的展開操作符扎谎。
迭代器對象
現(xiàn)在,你將無須親自從零開始實(shí)現(xiàn)一個(gè)對象迭代器烧董,我們會(huì)在下一篇文章詳細(xì)講解毁靶。為了幫助你理解本文,我們簡單了解一下迭代器(如果你跳過這一章逊移,你將錯(cuò)過非常精彩的技術(shù)細(xì)節(jié))预吆。
for-of循環(huán)首先調(diào)用集合的Symbol.iterator方法,緊接著返回一個(gè)新的迭代器對象胳泉。迭代器對象可以是任意具有.next()方法的對象拐叉;for-of循環(huán)將重復(fù)調(diào)用這個(gè)方法岩遗,每次循環(huán)調(diào)用一次。舉個(gè)例子凤瘦,這段代碼是我能想出來的最簡單的迭代器:
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è)無限循環(huán)。當(dāng)然笔诵,一般來說迭代器不會(huì)如此簡單返吻。
這個(gè)迭代器的設(shè)計(jì),以及它的.done和.value屬性乎婿,從表面上看與其它語言中的迭代器不太一樣测僵。在Java中,迭代器有分離的.hasNext()和.next()方法谢翎。在Python中恨课,他們只有一個(gè).next() 方法,當(dāng)沒有更多值時(shí)拋出StopIteration異常岳服。但是所有這三種設(shè)計(jì)從根本上講都返回了相同的信息剂公。
迭代器對象也可以實(shí)現(xiàn)可選的.return()和.throw(exc)方法。如果for-of循環(huán)過早退出會(huì)調(diào)用.return()方法吊宋,異常纲辽、break語句或return語句均可觸發(fā)過早退出。如果迭代器需要執(zhí)行一些清潔或釋放資源的操作璃搜,可以在.return()方法中實(shí)現(xiàn)拖吼。大多數(shù)迭代器方法無須實(shí)現(xiàn)這一方法。.throw(exc)方法的使用場景就更特殊了:for-of循環(huán)永遠(yuǎn)不會(huì)調(diào)用它这吻。但是我們還是會(huì)在下一篇文章更詳細(xì)地講解它的作用吊档。
現(xiàn)在我們已了解所有細(xì)節(jié),可以寫一個(gè)簡單的for-of循環(huán)然后按照下面的方法調(diào)用重寫被迭代的對象唾糯。
首先是for-of循環(huán):
for (VAR of ITERABLE) {
一些語句
}
然后是一個(gè)使用以下方法和少許臨時(shí)變量實(shí)現(xiàn)的與之前大致相當(dāng)?shù)氖纠∨穑?/p>
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
VAR = $result.value;
一些語句
$result = $iterator.next();
}
這段代碼沒有展示.return()方法是如何處理的,我們可以添加這部分代碼移怯,但我認(rèn)為這對于我們正在講解的內(nèi)容來說過于復(fù)雜了香璃。for-of循環(huán)用起來很簡單,但是其背后有著非常復(fù)雜的機(jī)制舟误。
我何時(shí)可以開始使用這一新特性葡秒?
目前,對于for-of循環(huán)新特性,所有最新版本Firefox都(部分)支持(譯注:從FF 13開始陸續(xù)支持相關(guān)功能眯牧,F(xiàn)F 36 - FF 40基本支持大部分特性)蹋岩,在Chrome中可以通過訪問 chrome://flags 并啟用“實(shí)驗(yàn)性JavaScript”來支持。微軟的Spartan瀏覽器支持学少,但是IE不支持剪个。如果你想在web環(huán)境中使用這種新語法,同時(shí)需要支持IE和Safari旱易,你可以使用Babel或Google的Traceur這些編譯器來將你的ES6代碼翻譯為Web友好的ES5代碼禁偎。
而在服務(wù)端傲武,你不需要類似的編譯器吞彤,io.js中默認(rèn)支持ES6新語法(部分)读拆,在Node中需要添加--harmony選項(xiàng)來啟用相關(guān)特性肩碟。
{done: true}
喲弧哎!
好的露戒,我們今天的講解就到這里硕旗,但是對于for-of循環(huán)的使用遠(yuǎn)沒有結(jié)束争舞。
在ES6中有一種新的對象與for-of循環(huán)配合使用非常契合士修,我沒有提及它因?yàn)樗俏覀兿轮芪恼碌闹黝}枷遂,我認(rèn)為這種新特性是ES6種最夢幻的地方,如果你尚未在類似Python和C#的語言中遇到它棋嘲,你一開始很可能會(huì)發(fā)現(xiàn)它令人難以置信酒唉,但是這是編寫迭代器最簡單的方式,在重構(gòu)中非常有用沸移,并且它很可能改變我們書寫異步代碼的方式痪伦,無論是在瀏覽器環(huán)境還是服務(wù)器環(huán)境,所以雹锣,下周的深入淺出 ES6 中网沾,請務(wù)必一起來仔細(xì)看看 ES6 的生成器:generators。