如何循環(huán)一個(gè)數(shù)組堕担?20年前 JavaScript 誕生的時(shí)候溅话,你會(huì)這么寫(xiě):
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
ES5 之后菌瘫,可以使用內(nèi)置的 forEach
方法:
myArray.forEach(function (value) {
console.log(value);
});
這樣就稍微短了點(diǎn)了熊户,但是仍然有一個(gè)小的缺點(diǎn):無(wú)法使用 break
語(yǔ)句跳出循環(huán),或者使用 return
從函數(shù)體內(nèi)返回验懊。
要是有可以用 for
循環(huán)的語(yǔ)法遍歷數(shù)組的所有元素擅羞,那該多好。
那么for-in
循環(huán)如何义图?
for (var index in myArray) { // 不要真的這樣寫(xiě)
console.log(myArray[index]);
}
這樣寫(xiě)有以下幾個(gè)問(wèn)題:
- 代碼中賦值為
index
的值是字符串"0", "1","2"等祟滴,而不是真是的數(shù)字。由于你不想要碰到字符串計(jì)算("2" + 1 == "21")的狀況歌溉,這對(duì)于編程而言是極其不方便的。 - 循環(huán)體不僅僅會(huì)遍歷數(shù)組元素,還會(huì)遍歷任意其他的自定義添加的屬性痛垛。例如草慧,如果數(shù)組包含了一個(gè)不能枚舉的屬性
myArray.name
,那么這次循環(huán)就會(huì)在index == "name"
的時(shí)候額外執(zhí)行一遍匙头。甚至數(shù)組原型鏈上的屬性也都會(huì)被遍歷到漫谷。 - 最讓人感到驚奇的是,在某些狀況下蹂析,這段代碼會(huì)以隨機(jī)順序循環(huán)數(shù)組元素舔示。
簡(jiǎn)而言之,for-in
循環(huán)在設(shè)計(jì)之初就是用于普通的以字符串為 key 值的對(duì)象的語(yǔ)法电抚,而不適用與數(shù)組惕稻。
強(qiáng)大的 for-of 循環(huán)
讓我們來(lái)看看 for-of
循環(huán):
for (var value of myArray) {
console.log(value);
}
唔,上述代碼就是看起來(lái)并沒(méi)有很強(qiáng)大蝙叛,對(duì)嗎俺祠?好吧,我們之后會(huì)探索 for-of
循環(huán)隱藏的強(qiáng)大之處借帘。就現(xiàn)在而言蜘渣,只需要記住:
- 這是最簡(jiǎn)潔肺然、直白的循環(huán)數(shù)組元素的方法
- 可以避免所有
for-in
循環(huán)的陷阱 - 不同于
forEach()
蔫缸,可以使用break
,continue
和return
for-in
循環(huán)用以遍歷對(duì)象的屬性。
for-of
循環(huán)用以遍歷數(shù)據(jù) -- 就像數(shù)組中的值一樣
但這還不是所有的內(nèi)容际起。
其他集合也支持 for-of 進(jìn)行遍歷
for-of
循環(huán)不僅僅支持?jǐn)?shù)組的遍歷拾碌。同樣適用于很多類(lèi)似數(shù)組的對(duì)象,例如 DOM NodeList加叁。
它也支持字符串的遍歷倦沧,會(huì)把字符串作為一組 Unicode 的字符進(jìn)行遍歷:
for (var chr of "????") {
alert(chr);
}
它也可以應(yīng)用于 Map 和 Set 對(duì)象(ES6中新增的數(shù)據(jù)結(jié)構(gòu),之后的文章中會(huì)提及)它匕。
例如展融,Set 對(duì)象可以有效的去重:
// 從一個(gè)單詞組成的數(shù)組聲明一個(gè)新的 Set
var uniqueWords = new Set(words);
然后你就輕松可以遍歷 Set 中的內(nèi)容了:
for (var word of uniqueWords) {
console.log(word);
}
Map 則稍微有點(diǎn)不同:Map 中的數(shù)據(jù)是由鍵值對(duì)組成的,所以你需要使用將其中的鍵值解構(gòu)為兩個(gè)獨(dú)立的變量:
for (var [key, value] of phoneBookMap) {
console.log(key + "'s phone number is: " + value);
}
解構(gòu) (Destructing) 也是 ES6 中的特性豫柬,同樣會(huì)在之后的文章中提及告希。
到現(xiàn)在為止,你可能已經(jīng)可以想象到: JavaScript 已經(jīng)有了一些新的集合類(lèi)型烧给,且在不久的將來(lái)會(huì)出現(xiàn)更多燕偶。而 for-of
則是被設(shè)計(jì)出來(lái)用以在這些集合上使用的循環(huán)語(yǔ)句。
for-of
并不適用于處理原有的原生對(duì)象础嫡,但是如果你想要遍歷對(duì)象的屬性指么,可以使用 for-in
或者內(nèi)置的 Object.keys()
:
// 輸出對(duì)象自身可以枚舉的值
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}
深入本質(zhì)
能工摹形酝惧,巧匠竊意。 -- 畢加索
ES6 中新增的特性都不是憑空出現(xiàn)的伯诬,大部分特性的優(yōu)點(diǎn)都已經(jīng)在其他的編程語(yǔ)言中被證實(shí)過(guò)晚唇。
例如 for-of
循環(huán)與 C++, Java, C#和 Python中的循環(huán)語(yǔ)句十分類(lèi)似。與這些語(yǔ)言一樣盗似,for-of
循環(huán)適用于語(yǔ)言本身以及標(biāo)準(zhǔn)庫(kù)中的不同的數(shù)據(jù)類(lèi)型哩陕。同時(shí)它也是這門(mén)語(yǔ)言的一個(gè)擴(kuò)展點(diǎn)。
如同其他語(yǔ)言中的 for/foreach 語(yǔ)句赫舒,for-of
通過(guò)方法調(diào)用來(lái)實(shí)現(xiàn)集合的遍歷悍及。數(shù)組、Maps接癌、Sets 以及其他我們討論過(guò)的對(duì)象之間有個(gè)共同點(diǎn):有迭代器方法心赶。
當(dāng)然,任何對(duì)象都可以添加迭代器方法扔涧。
就像你可以給任意對(duì)象添加 myObject.toString()
方法园担,使之可以將對(duì)象轉(zhuǎn)換為字符串,你可以將 myObject[Symbol.iterator]()
方法添加到任意對(duì)象枯夜,這樣對(duì)象就可以被遍歷了弯汰。
例如,假設(shè)你正在使用 jQuery湖雹,盡管你非常喜歡 .each()
方法咏闪,但是你仍希望 jQuery 對(duì)象可以支持 for-of
循環(huán)。以下就是實(shí)現(xiàn)的方法:
// jQuery 對(duì)象與數(shù)組類(lèi)似
// 賦予他們和數(shù)組一樣的迭代器方法
jQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
我知道你在想什么摔吏。[Symbol.iterator]
語(yǔ)法看起來(lái)非常奇怪鸽嫂。這段代碼到底做了什么事?它在此處通過(guò) Symbol
處理了方法名征讲。標(biāo)準(zhǔn)委員會(huì)稱(chēng)之為 .iterator()
据某,也許你已經(jīng)有些代碼包含了 .iterator()
方法,這確實(shí)會(huì)讓你感到困惑诗箍。因此標(biāo)準(zhǔn)使用了symbol 作為這個(gè)方法的名稱(chēng)而不是一個(gè)字符串癣籽。
Symbols 是 ES6 中的新特性 -- 這將會(huì)在以后的博文中介紹。就現(xiàn)在而言滤祖,你只需知道標(biāo)準(zhǔn)可以定義一種全新的 symbol 筷狼,例如 Symbol.iterator
,這可以保證與現(xiàn)有的代碼不沖突匠童。這樣做的代價(jià)就是語(yǔ)法看起來(lái)略顯奇怪埂材。但是這僅僅是這種語(yǔ)法帶來(lái)的那么多特性與向后兼容性所造成的輕微代價(jià)。
一個(gè)包含[Symbol.iterator]()
方法的對(duì)象被稱(chēng)之為可迭代汤求。在接下來(lái)的文章中俏险,我們可以看到可迭代對(duì)象的概念貫穿了JavaScript 語(yǔ)言严拒,不僅僅是 for-in
,還有 Map
和 Set
的構(gòu)造函數(shù)竖独,解構(gòu)賦值以及一種新的展開(kāi)操作符糙俗。
迭代器對(duì)象
現(xiàn)在,你無(wú)須自己從頭實(shí)現(xiàn)一個(gè)迭代器對(duì)象预鬓。但是從本文的完整性的角度而言,我們需要了解一下迭代器對(duì)象赊颠。(如果你跳過(guò)了這一整節(jié)內(nèi)容格二,你會(huì)錯(cuò)過(guò)很多精彩的技術(shù)細(xì)節(jié))
for-of
循環(huán)在集合中先調(diào)用 [Symbol.iterator]()
方法。然后返回一個(gè)新的迭代器對(duì)象竣蹦。一個(gè)迭代器對(duì)象可以是任意包含 .next()
方法的對(duì)象顶猜;for-of
會(huì)在循環(huán)的過(guò)程中重復(fù)調(diào)用這個(gè)方法。例如痘括,以下是一個(gè)我所能想到的最簡(jiǎn)單的迭代器對(duì)象:
var zeroesForeverIterator = {
[Symbol.iterator]: function () {
return this;
},
next: function () {
return {done: false, value: 0};
}
};
每當(dāng)這個(gè) .next()
方法被調(diào)用的時(shí)候长窄,它都會(huì)返回相同的結(jié)果,以此告知 for-of 循環(huán)(a) 還沒(méi)有結(jié)束迭代纲菌;(b) 下一個(gè)值是 o
挠日。這也就意味著 (value of zeroesForeverIterator){}
是一個(gè)無(wú)限循環(huán)體。當(dāng)然翰舌,一個(gè)典型的迭代器不會(huì)如此簡(jiǎn)單嚣潜。
JavaScript 中的迭代器及它的 .done
和 .value
屬性在表面上看起來(lái)和其他語(yǔ)言中的迭代器的不同。在 Java 中椅贱,迭代器有兩個(gè)獨(dú)立的 .hasNext()
和 .next()
方法懂算。在 Python 中,只有一個(gè) .next()
方法庇麦,在沒(méi)有更多值的時(shí)候會(huì)拋出 StopIteration
異常计技。但是這三種設(shè)計(jì)從根本上而言,都返回了一樣的信息山橄。
一個(gè)迭代器對(duì)象也可以實(shí)現(xiàn)可選的 .return()
和 .throw(exc)
方法垮媒。for-of
會(huì)在循環(huán)過(guò)早結(jié)束的時(shí)候調(diào)用 .return()
方法,這可能是因?yàn)楫惓<莸āreak 或者 return 語(yǔ)句涣澡。迭代器可以實(shí)現(xiàn) .return()
,如果它需要做一些清理或者釋放正在使用的資源的操作丧诺。大多數(shù)迭代器都不需要去實(shí)現(xiàn)這個(gè)方法入桂。.throw(exc)
則應(yīng)用于更特殊的情況:for-of
永遠(yuǎn)不會(huì)調(diào)用它。我們會(huì)在之后的文章中詳細(xì)討論驳阎。
既然我們已經(jīng)了解了所有的細(xì)節(jié)抗愁,現(xiàn)在來(lái)使用一個(gè)簡(jiǎn)單的 for-of
循環(huán)馁蒂,然后用底層的方法重寫(xiě)之:
首先,是一個(gè) for-of
循環(huán):
for (VAR of ITERABLE) {
STATEMENTS
}
接下來(lái)是一個(gè)大致等價(jià)的實(shí)現(xiàn)蜘腌,使用了底層的方法和一些臨時(shí)變量:
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
VAR = $result.value;
STATEMENTS
$result = $iterator.next();
}
這段代碼沒(méi)有展示 .return()
是如何被處理的沫屡。我們可以在代碼中添加上去,但是我覺(jué)得這樣反而會(huì)使其變得晦澀撮珠。for-of
使用起來(lái)非常簡(jiǎn)單沮脖,但是背后卻有很多值得學(xué)習(xí)的地方。
什么時(shí)候可以開(kāi)始使用這一特性芯急?
所有當(dāng)前的 Firefox 版本都已經(jīng)支持了 for-of
循環(huán)勺届。在 Chrome 中使用需要到 chrome://flags
中開(kāi)啟 "Experimental JavaScript"。微軟的 Spartan 瀏覽器中也可以使用娶耍,不過(guò)沒(méi)有在已經(jīng)發(fā)布的 IE 中被支持免姿。如果你想要使用這種新的語(yǔ)法,但是想要支持 IE 和 Safari榕酒,可以使用像 Babel 或者 Google 的 Traceur 編譯器將你的 ES6 代碼轉(zhuǎn)換為 Web 友好的 ES5 代碼胚膊。
而在服務(wù)端,則不需要編譯器的幫助 -- 現(xiàn)在你就可以在 io.js 中使用 for-of
(而在 Node中想鹰,需要加入 --harmony
選項(xiàng))紊婉。
這個(gè)系列的譯文與原文一致遵守CC BY-SA 3.0 協(xié)議。