深入淺出ES6(二):迭代器和for-of循環(huán)

本文為前端之巔原創(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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蕊爵,一起剝皮案震驚了整個(gè)濱河市辉哥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌攒射,老刑警劉巖醋旦,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匆篓,居然都是意外死亡浑度,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門鸦概,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事窗市∠瓤叮” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵咨察,是天一觀的道長论熙。 經(jīng)常有香客問我,道長摄狱,這世上最難降的妖魔是什么脓诡? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮媒役,結(jié)果婚禮上祝谚,老公的妹妹穿的比我還像新娘。我一直安慰自己酣衷,他們只是感情好交惯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著穿仪,像睡著了一般席爽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啊片,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天只锻,我揣著相機(jī)與錄音,去河邊找鬼紫谷。 笑死齐饮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碴里。 我是一名探鬼主播沈矿,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼咬腋!你這毒婦竟也來了羹膳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤根竿,失蹤者是張志新(化名)和其女友劉穎陵像,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寇壳,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡醒颖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了壳炎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泞歉。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腰耙,到底是詐尸還是另有隱情榛丢,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布挺庞,位于F島的核電站晰赞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏选侨。R本人自食惡果不足惜掖鱼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望援制。 院中可真熱鬧戏挡,春花似錦、人聲如沸隘谣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寻歧。三九已至掌栅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間码泛,已是汗流浹背猾封。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留噪珊,地道東北人晌缘。 一個(gè)月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像痢站,于是被迫代替她去往敵國和親磷箕。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容