【探秘ES6】系列专栏(二):迭代器和for-of循环
為什么80%的碼農都做不了架構師?>>> ??
ES6作為新一代JavaScript標準,即將與廣大前端開發者見面。為了讓大家對ES6的諸多新特性有更深入的了解,Mozilla Web開發者博客推出了《ES6 In Depth》系列文章。CSDN已獲授權,將持續對該系列進行翻譯,組織成【探秘ES6】系列專欄,供大家學習借鑒。本文為該系列的第二篇。?
你是如何遍歷數組中的元素的?20年前JavaScript剛進入視野時,你應該是這樣寫的:
for?(var?index?=?0;?index?<?myArray.length;?index++)?{???console.log(myArray[index]);??? }直到ES5中原生JavaScript中添加了forEach方法:
語法上簡潔了一些,但是它有一個小小的不足:你不能用break語句跳出循環且不能在這個封閉的函數內使用return語句。
如果有一個簡單的for-loop語法來遍歷數組就好了。
使用一個for-in循環怎么樣?
for?(var?index?in?myArray)?{?????//?don't?actually?do?this???console.log(myArray[index]);??? }我用幾個理由來說明這并不是一個好主意:
數組的索引值index是String類型的“0”,“1”,“2”等等,而不是Number類型。當你進行算術運算時(“2”+1==“21”)也許并不是你期望的結果,所以運算前需要類型轉換,這很不方便。
循環體不僅會遍歷數組的元素,甚至連expando屬性也遍歷出來了。舉個例子,如果你的myArray數組中有一個叫做name的屬性,遍歷時就將 index ==”name”也遍歷出來,這樣就多了一次執行。即時這些屬性在數組的原型鏈上是可直接訪問的。
最讓人無語的是,在某些情況下,這段代碼在遍歷數組元素時順序是任意的。
總而言之,for-in語法是被設計來遍歷普通的“鍵值對”對象的,不適合用在數組上。
強大的for-of循環
還記得我上篇提到的ES6是向后兼容的嗎。即使在遍歷數組的時候,成千上萬的網站使用了for-in循環。所有“修復”for-in讓它更適用于數組是有必要的。ES6來解決這個問題的唯一途徑是新增一個新的遍歷語法。
新語法如下:
for?(var?value?of?myArray)?{???console.log(value);??? }恩?!從構建上來說好像并沒什么改變,事實如此嗎?當然不是,我們來看看for-of的葫蘆里究竟賣的什么藥。首先,只需要注意這幾點:
這是目前遍歷數組最簡潔和直接的語法;
它避免了for-in的所有缺陷;
與forEach()不一樣,它支持break,continue和return。
for-in循環用于遍歷對象屬性。
for-of循環用于遍歷數據——比如數組中單值。
其它集合也支持for-of
for-of循環不僅僅是為遍歷數組而設計的。基本上所有類數組對象都適用,比如DOM NodeListS。
也能用在字符串上,它將字符串當做一個Unicode字符序列:
?
它也能用在Map和Set對象上。
哦,不好意思,你沒聽說過Map和Set?沒關系,他們是出現在ES6中的新成員。有機會我們會寫個完整的關于它的文章。如果你使用過其它編程語言中的maps和sets,那么你也不會有陌生感。
例如,一個set對象使用于排除重復項:
//?make?a?set?from?an?array?of?words??? var?uniqueWords?=?new?Set(words);如果你想遍歷你的set,很簡單:
for?(var?word?of?uniqueWords)?{???console.log(word);??}Map有一點不同:它里面的數據由鍵值對組成,所以你需要使用destructuring將“鍵”和“值”解構為兩個獨立的變量:
for?(var?[key,?value]?of?phoneBookMap)?{???console.log(key?+?"'s?phone?number?is:?"?+?value);??? }Destructuring(解構)也是ES6的新特性,在未來博客中會有很多關于它的文章。
目前為止,你可以這樣理解:JS已經有了幾個不同的集合類,而且更多的集合類正在被添加進來。for-of循環語句的設計初衷就是適用于所有這些集合類。
for-of并不能用于普通的舊對象。如果你想要遍歷對象的所有屬性,可以使用for-in,也可以通過Object.keys(object)將對象的所有屬性以數組形式返回后再使用for-of。?
//?dump?an?object's?own?enumerable?properties?to?the?console???for?(var?key?of?Object.keys(someObject))?{??? console.log(key?+?":?"?+?someObject[key]);??? }深入理解
“能工摹形,巧匠竊意。”——巴勃羅·畢加索
JavaScript在ES6中所新增的特性并不是憑空而來,大多數都是借鑒于其它優秀的語言。
以for-of循環偽例,與C++、Java、C#和Python的循環語句非常類似。和它們一樣,支持該種語言提供的多種數據解構和標準庫。但是它也是該種語言的一個擴展點。
就像for/foreach語句在其它語言中一樣,for-of的執行完全靠方法調用。像Arrays,Maps,Sets等我們提到過的對象都有一個共同點就是它們都有一個遍歷的方法。
其它類型的對象也都可有一個遍歷方法:任何對象都可以。
就像你可以對任何一個對象添加方法myObject.toString()讓JS知道如何將對象轉換為字符串一樣,你也可以對任何對象添加方法myObject.toString()來告訴JS如何遍歷這個對象。
例如,假設你使用的jQuery,盡管喜歡使用.each(),那你也會喜歡上在jQuery對象中使用for-of。請看下面這個例子:
//?Since?jQuery?objects?are?array-like,??? //?give?them?the?same?iterator?method?Arrays?have??jQuery.prototype[Symbol.iterator]?=??? Array.prototype[Symbol.iterator];好吧,我知道你會覺得[Symbol.iterator] 這樣的語法看起來很奇怪。它是怎么執行的呢?使用方法名就可以了。標準委員會剛剛將這個方法命名為.iterator(),但是你已存在的代碼中可能已經有了叫做.iterator的方法,那會造成命名沖突,讓人傻傻分不清。因此所有標準庫將其封裝進了symbol,而不是使用簡單的用字符串來直接命名。
Symbols是ES6的新特性,我們將在以后的博客中討論它。目前,你所需要知道的是現在標準定義了一個全新的symbol,比如Symbol.iterator,為了保證與已存在的代碼不存在命名沖突,所以這個代價就是語法看起來有點奇怪。
為了這個優秀的新特性的向后兼容性,這點小代價也就微不足道了。
迭代器對象
從現在開始你再也沒有必要為自己寫一個迭代器對象了,這個我們在下篇文章中再來討論。但是出于完整性的考慮,讓我們先來看看一個迭代器對象是什么樣子的。(如果你跳過這一節,你會錯過很多有趣的技術細節喲)。
for-of循環開始于對集合的[Symbol.iterator]()方法的調用。它會返回一個新的迭代器對象。任意一個有.next()方法的對象都可以被稱作迭代器對象;每次執行進入循環時,for-of方法將會用.next()方法。例如,下面是一個我所能想到的最簡單的迭代器構造:
var?zeroesForeverIterator?=?{??[Symbol.iterator]:?function?()?{??return?this;???},???next:?function?()?{???return?{done:?false,?value:?0};???}??};每次當.next()方法被調用的時候,它會返回相同的結果,告訴for-of循環:(1)我們還沒結束迭代;(2)下一個值是0。這意味著,(value of zeroesForeverIterator) {}將是一個無線循環。當然,一個真正的迭代器并不會這么簡單。
迭代器的設計,伴隨著.done和.value屬性,從表面上來看似乎和其它語言中的迭代器不太一樣。在Java中,迭代器將.hasNext()和.next()區分為兩個方法。在Python中,它只有一個.next()方法,當沒有下一個值時會拋出StopIteration 。但是從根本上來說,這三種方法返回同樣的信息。
迭代器也可以實現一些可選方法,比如.return()和throw(ext)。在for-of循環中,當遇到異常或者break和return語句時可以調用.return()方法提前退出循環。迭代器可以通過實現.return()方法來清空變量或釋放當前資源,大多數迭代器對象是使用不到這一點的。.throw(exc)是一個特殊的例子:for-of完全使用不到它,我們下次再來討論。
現在我們已經了解了所有的基本細節,我們可以寫一個簡單的循環并重寫它的底層方法調用部分。
先寫一個for-of循環:
for?(VAR?of?ITERABLE)?{???STATEMENTS??? }下面這段代碼使用簡單的底層方法和幾個簡單的變量來實現同樣的功能:
var?$iterator?=?ITERABLE[Symbol.iterator]();?? var?$result?=?$iterator.next();?? while?(!$result.done)?{??VAR?=?$result.value;??STATEMENTS??$result?=?$iterator.next();?? }這段代碼并沒有體現出.return()操作。我們可以添加進來,但是我認為認清它的執行過程比闡明它更重要。for-of的使用起來很簡單,但有很多看不見幕后的工作。
我什么時候才能使用它?
當前所有的Firefox releases版本都支持for-of循環。如果你想在Chrome中使用,到chrome://flags設置“Experimental JavaScript”為“開啟”即可。微軟的Spartan瀏覽器支持它,但是IE不支持。如果你想要在Web中使用這些新語法且不用考慮支持IE和Safari,你可以使用Babel或者谷歌的Traceur這樣的編譯器將你的ES6代碼轉換成兼容性友好的ES5。
在服務端,你不需要一個編譯器——你可以在io.js(基于Node,是一個不錯的選擇)中使用for-of。
(更新:在Chrome中默認是禁用的,這個被我忽視掉了,感謝Oleg 指出。)
講完啦!
我們今天的計劃都完成了,但是我們對for-of循環的學習還沒結束。
ES6中還有一個和for-of完美結合的新對象。我之所以沒提到它是因為它是我們下次的主題。
我認為它是ES6中最神奇的新特性。如果你之前沒在像Python和C#這樣的語言中使用過它,一開始它可能會讓你感到難以置信。無論在客戶端還是服務端,這是寫一個構造器最簡單方法,對于重構很有用,它有可能會改變我們寫異步代碼的方式習慣。
下次將一起深入ES6的Generators 。(譯者:向渝 責編:陳秋歌)
原文鏈接:ES6 In Depth: Iterators and the for-of loop
本譯文遵循Creative Commons Attribution Share-Alike License v3.0?
轉載于:https://my.oschina.net/1pei/blog/520584
總結
以上是生活随笔為你收集整理的【探秘ES6】系列专栏(二):迭代器和for-of循环的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Tomcat配置优化
- 下一篇: 大道至简第四章阅读笔记