ES6做爲新一代JavaScript標準,即將與廣大前端開發者見面。爲了讓你們對ES6的諸多新特性有更深刻的瞭解,Mozilla Web開發者博客推出了《ES6 In Depth》系列文章。CSDN已獲受權,將持續對該系列進行翻譯,組織成【探祕ES6】系列專欄,供你們學習借鑑。本文爲該系列的第二篇。
前端
你是如何遍歷數組中的元素的?20年前JavaScript剛進入視野時,你應該是這樣寫的:es6
for (var index = 0; index < myArray.length; index++) { console.log(myArray[index]); }
直到ES5中原生JavaScript中添加了forEach方法:
chrome
myArray.forEach(function (value) { console.log(value); });
語法上簡潔了一些,可是它有一個小小的不足:你不能用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」也遍歷出來,這樣就多了一次執行。即時這些屬性在數組的原型鏈上是可直接訪問的。函數
最讓人無語的是,在某些狀況下,這段代碼在遍歷數組元素時順序是任意的。oop
總而言之,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