我們在學習web前端的路程起步時總是疑問,我們如何更好的遍歷元素呢?迭代器和生成器是什么?今天為大家帶上與精彩的ES6有關的遍歷數據的一些方法和對迭代器和生成器的介紹。
一、for-of循環
(1)for循環的疑問
起初我們如何遍歷數組中的元素呢?20年前JavaScript
剛萌生時,你可能這樣實現數組遍歷:
var myArray = ['2','3','aaa'];
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
自ES5正式發布后,你可以使用內建的forEach方法來遍歷數組:
myArray.forEach(function (value) {
console.log(value);
});
使用forEach
的這段代碼看起來更加簡潔,但這種方法也有一個小缺陷:你不能使用break
語句中斷循環,也不能使用return
語句返回到外層函數。
是不是呢?你們可以進行相關的驗證,同時呢它在瀏覽器兼容與支持方面并不理想,所以我們會想起for循環
來。
當然,如果只用for循環
的語法來遍歷數組元素也很不錯,就如上面的第一個例子。
那么,你一定想嘗試一下for-in
循環:
for (var index in myArray) { // 千萬別這樣做
console.log(myArray[index]);
}
這絕對是一個糟糕的選擇,為什么呢?
- 在這段代碼中,賦給
index
的值不是實際的數字,而是字符串“0”、“1”、“2”,而你呢?此時很可能在無意之間需要進行相關計算,但這是這不是數值的計算了,而是字符串算數計算,例如:“2” + 1 == “21”,這給編碼過程帶來極大的不便。
- 作用于數組的for-in循環體除了遍歷數組元素外,還會遍歷自定義屬性。舉個例子,如果你的數組中有一個可枚舉屬性
myArray.name
,循環將額外執行一次,遍歷到名為“name”的索引。就連數組原型鏈上的屬性都能被訪問到。 - 最讓人震驚的是,在某些情況下,這段代碼可能按照隨機順序遍歷數組元素。
- 簡而言之,for-in是為普通對象設計的,你可以遍歷得到字符串類型的鍵,因此不適用于數組遍歷。
其實啰里啰唆說了這么多你就記住一句話:for-in是用于遍歷對象的,普通for循環和forEach
是遍歷普通數組的。
如果有天你想要遍歷對象又要得到它的鍵值,這里舉例說明:
var obj = {
'name': '言墨兒',
'age': 21
};
console.log(Object.keys(obj)) // 返回的是一個數組
// 不推薦for-in
for (var val in Object.keys(obj)) {
console.log(val); // 獲得鍵值key-字符串
console.log(Object.keys(obj)[val]); // name、age-(獲得鍵值key對應的數據)
}
// 推薦for循環
for (var i = 0; i < Object.keys(obj).length; i++) {
console.log(i); // 獲得鍵值key-數字
console.log(Object.keys(obj)[i]); // name、age-(獲得鍵值key對應的數據)
}
(2)強大的for-of循環
為了解決這些問題所以新的循環方式在ES6中出現了,當然ES6不會破壞你已經寫好的JS代碼。目前,成千上萬的Web網站依賴for-in
循環,其中一些網站甚至將其用于數組遍歷。而如果想通過修正for-in
循環增加數組遍歷支持會讓這一切變得更加混亂,因此,標準委員會在ES6中增加了一種新的循環語法來解決目前的問題。
就像這樣:
// for-of循環支持數組遍歷和大多數類數組對象
var myArray = ['2','3','aaa']
for (var value of myArray) {
console.log(value); // 2,3,aaa
}
是的,與之前的內建方法相比,這種循環方式看起來是否有些眼熟?那好,我們將要探究一下for-of循環的外表下隱藏著哪些強大的功能。現在,你只需記住:
- 這是最簡潔、最直接的遍歷數組元素的語法
- 這個方法避開了for-in循環的所有缺陷
- 與forEach()不同的是,它可以正確響應break、continue和return語句
表面看來for-in
循環用來遍歷對象屬性,for-of
循環用來遍歷數據—例如數組中的值。但是,for-of
不僅如此!
for-of
循環也可以遍歷其它的集合,for-of
循環不僅支持數組,還支持大多數類數組對象,例如DOM NodeList對象。同時,for-of
循環也支持字符串遍歷,它將字符串視為一系列的Unicode字符來進行遍歷:
for (var chr of "yanmoer") {
console.log (chr); // y,a,n,m,o,e,r
}
它同樣支持Map
和Set
對象遍歷。
很多人也許沒聽說過Map和Set對象。他們是ES6中新增的類型。不知道也沒有關系,我將在后面簡單講解這兩個新的類型,如果你還有興趣,你可以點擊我存放的連接。
如果你曾在其它語言中使用過Map
和Set
對象,你會發現ES6中的并無太大出入。
其實講簡單點就是Set
對象可以自動排除重復項:
// 基于單詞數組創建一個set對象
var words = ['yanmoer','qinni','yanmoer']
var uniqueWords = new Set(words); // 如果成為Set對象,輸出結果會去掉重復項
console.log(uniqueWords); // Set {"yanmoer", "qinni"}
// 生成Set對象后,你可以輕松遍歷它所包含的內容:
for (var word of uniqueWords) {
console.log(word); // yanmoer,qinni
}
Set
對象還有一些方法,這里不一一列舉了,詳情請見官網api:Set
對象
Map
對象則稍有不同:內含的數據由鍵值對組成,所以你需要使用解構(destructuring)來將鍵值對拆解為兩個獨立的變量,例子說明:
var phoneBookMap = [['name','yanmo'],['age','22']]; // Map對象就是這樣的
for (var [key, value] of phoneBookMap) { // [key, value]就是解構
console.log(key + " : " + value); //'name' : 'yanmo','age' : '22'
}
其實Map
對象就是簡單的鍵/值映射。其中鍵和值可以是任意值(對象或者原始值)。關于Map
對象的方法,也是詳情請見官網api:Map
對象
解構也是ES6的新特性,簡單說來解構賦值就是允許你使用類似數組或對象字面量的語法將數組和對象的屬性賦給各種變量,我將在后續的文章中進行講解。看來我應該記錄這些優秀的問題,未來再進行相關新內容的一一剖析。
現在,你只需記住:未來的JS可以使用一些新型的集合類,甚至會有更多的類型陸續誕生,而
for-of
就是為遍歷所有這些集合特別設計的循環語句。
for-of循環不支持普通對象,你應當用用for-in循環(這也是它的本職工作)或內建的Object.keys()方法:
// 向控制臺輸出對象的可枚舉屬性
var someObject = {
classA: 'textColor',
classB: 'textSize',
isA: false
}
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]); // classA: 'textColor',classB: 'textSize',isA: false
}
二、深入理解
“能工摹形,巧匠竊意。”——巴勃羅?畢卡索
前言
結合我的實踐和運用,ES6及以后ES的后繼者都會堅持這樣一個規律:凡是新加入的特性,勢必已在其它語言中得到強有力的實用性證明。
舉個例子,新加入的
for-of
循環像極了C++、Java、C#以及Python中的循環語句。與它們一樣,這里的for-of循環支持語言和標準庫中提供的幾種不同的數據結構。它同樣也是這門語言中的一個擴展點(關于擴展點,建議參考 1. 淺析擴展點 2. What are extensions and extension points?,連接給你們了有興趣了可以研究,沒興趣了也沒關系,繼續)。
正如其它語言中的for/foreach
語句一樣,for-of
循環語句通過方法調用來遍歷各種集合。數組、Maps對象、Sets對象以及其它在我們討論的對象有一個共同點,它們都有一個迭代器方法。
你可以給任意類型的對象添加迭代器方法。
當你為對象添加myObject.toString()
方法后,就可以將對象轉化為字符串,同樣地,當你向任意對象添加myObject[Symbol.iterator]()
方法,就可以遍歷這個對象了。你們一定很疑問那個[Symbol.iterator]
語法是什么情況,這段代碼到底做了什么呢?這里通過Symbol
處理了一下方法的名稱,ES標準委員會可以把這個方法命名為.iterator()
方法,但是如果你的代碼中的對象可能也有一些.iterator()
方法,這不是最好的解決方法。于是在ES6標準中使用symbol
來作為方法名,而不是使用字符串。同時,Symbols
也是ES6中的新類型。
現在,你需要記住,基于新標準,你可以定義一個全新的symbol
,就像Symbol.iterator
,如此一來可以保證不與任何已有代碼產生沖突。這樣做的代價是,這段代碼的語法看起來會略顯生硬,但是這微乎其微代價卻可以為你帶來如此多的新特性和新功能,并且你所做的這一切可以完美地向后兼容。
所有擁有[Symbol.iterator]()
的對象被稱為可迭代的。對于ES來說可迭代對象的概念幾乎貫穿于整門語言之中,不僅是for-of
循環,還有Map
和Set
構造函數、解構賦值,以及新的展開操作符。
如果你還看不明白,也沒關系,下面我會進行通俗易懂的說明。
(1)迭代器對象
現在,你將無須親自從零開始實現一個對象迭代器,我會在以后的文章詳細講解,在這里我們只簡單了解一下迭代器:
for-of
循環首先調用集合的[Symbol.iterator]()
方法,緊接著返回一個新的迭代器對象。迭代器對象可以是任意具有.next()
方法的對象;for-of
循環將重復調用這個方法,每次循環調用一次。
舉個例子,這段代碼是我能想出來的最簡單的迭代器,這里我們不添加for-of
循環進行控制臺的輸出:
// 對象迭代
var zeroesForeverIterator = {
[Symbol.iterator]: function () { // 簡而言之只是一個方法
return this;
},
next: function () { // next
return {done: false, value: 0};
}
};
console.log(zeroesForeverIterator.next()); // Object {done: false, value: 0}
console.log(zeroesForeverIterator[Symbol.iterator]()); // 獲得是zeroesForeverIterator我們自定義的對象
這里附上控制臺輸出截圖:
控制臺輸出截圖
如果我們加上for-of
循環:
var zeroesForeverIterator = {
[Symbol.iterator]: function () { // 簡而言之只是一個方法
return this;
},
next: function () { // next
return {done: false, value: 0};
}
};
console.log(zeroesForeverIterator.next()); // Object {done: false, value: 0}
console.log(zeroesForeverIterator[Symbol.iterator]()); // Object {}
for (var value of zeroesForeverIterator) {
console.log(value); // 0
}
這里附上控制臺輸出截圖:
控制臺輸出截圖
以及我瀏覽器崩潰的截圖:
瀏覽器崩潰截圖
相信大家也在網上其他地方看過,每當解釋這里都是亂說一通,到最后誰也不明白,經過我的敘述我想到這里大家也都了解了有些眉目,其實只要你細心,我們會發現如果我們加上for-of
循環,for-of
循環每一次調用.next()方法,它都返回相同的結果,其實返回給for-of循環的結果有兩種可能:
- 我們尚未完成迭代
- 下一個值為0。這意味著
for (var value of zeroesForeverIterator) {}
將會是一個無限循環。當然,一般來說迭代器不會如此簡單。
這個迭代器的設計,以及它的.done
和.value
屬性,從表面上看與其它語言中的迭代器不太一樣。在Java中,迭代器有分離的.hasNext()
和.next()
方法。在Python中,他們只有一個.next()
方法,當沒有更多值時拋出StopIteration
異常。
但是所有這三種設計從根本上講都返回了相同的信息。
迭代器對象也可以實現可選的.return()
和.throw(exc)
方法。如果for-of
循環過早退出會調用.return()
方法,異常、break語句或return語句均可觸發過早退出。如果迭代器需要執行一些清潔或釋放資源的操作,可以在.return()
方法中實現。大多數迭代器方法無須實現這一方法。.throw(exc)
方法的使用場景就更特殊了:for-of循環永遠不會調用它。但是我們還是會在以后更詳細地講解它的作用。
現在我們已了解所有細節,可以寫一個簡單的for-of循環然后按照下面的方法調用重寫被迭代的對象。
首先是for-of循環:
for (var val of obj) {
// 一些語句
}
然后是一個使用以下方法和少許臨時變量實現的與之前大致相當的示例:
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
val = $result.value;
一些語句
$result = $iterator.next();
}
這段代碼沒有展示.return()
方法是如何處理的,我們可以添加這部分代碼,但我認為這對于我們正在講解的內容來說過于復雜了。for-of
循環用起來很簡單,但是其背后有著非常復雜的機制。
下來就到了不得不說的生成器
(2)生成器
ES6生成器(Generators)簡介
什么是生成器?不懂得人一定會疑問,首先我們從一個示例開始:
function* quips(name) {
yield "你好 " + name + "!";
yield "希望你能喜歡這篇詳細介紹ES6的文章";
if (name.startsWith("X")) {
yield "你的名字" + name + "真他媽吊";
}
yield "我們下次再見!";
}
var iter = quips("讀者");
console.log(iter) // [object Generator]
console.log(iter.next()) // Object { value: "你好 jorendorff!", done: false }
console.log(iter.next())// Object { value: "希望你能喜歡這篇介紹ES6的譯文", done: false }
console.log(iter.next()) // Object { value: "我們下次再見!", done: false }
console.log(iter.next()) / Object { value: undefined, done: true }
控制臺輸出截圖:
控制臺輸出截圖
這是一只會說話的貓,這段代碼很可能代表著當今互聯網上最重要的一類應用。(因為你可以發現,這可以控制一個流程)。如果你沒有看懂,下面我會進行詳解。
這段代碼看起來很像一個函數,我們稱之為生成器函數,它與普通函數有很多共同點,但是二者有如下區別:
- 普通函數使用
function
聲明,而生成器函數使用function *
聲明,function *
只是一種寫法罷了。
- 在生成器函數內部,有一種類似
return
的語法:關鍵字yield
。二者的區別是,普通函數只可以return
一次,而生成器函數可以yield
多次(當然也可以只yield
一次)。在生成器的執行過程中,遇到yield
表達式立即暫停,后續可使用例如.next()
恢復執行狀態。
這就是普通函數和生成器函數之間最大的區別,普通函數不能自暫停,生成器函數可以。
生成器做了什么?
當你在我上面的例子中調用quips()生成器函數時發生了什么,那么生成器他就做了什么,寫的這么詳細,我想大家應該能看懂。
ES6生成器(Generators)案例詳解說明
我們大概已經習慣了普通函數的使用方式,當你調用它們時,它們立即開始運行,直到遇到return或拋出異常時才退出執行,作為JS程序員你一定深諳此道。
生成器調用看起來非常類似:quips("jorendorff")。但是,當你調用一個生成器時,它并非立即執行,而是返回一個已暫停的生成器對象(上述實例代碼中的iter)。你可將這個生成器對象視為一次函數調用,只不過立即凍結了,即在你調用生成器,并且還沒有使用過.next()
方法前,它就好像恰好在生成器函數的最頂端的第一行代碼之前凍結了,暫停在那里。
每當你調用生成器對象的.next()
方法時,函數調用將其自身解凍并一直運行到下一個yield
表達式,再次暫停。這也是在上述代碼中我們每次都調用iter.next()
的原因,我們獲得了quips()
函數體中yield
表達式生成的不同的字符串值。
當我們調用最后一個iter.next()
時,我們最終抵達生成器函數的末尾,所以返回結果中done
的值為true
。抵達函數的末尾意味著沒有返回值,所以返回結果中value
的值為undefined
。(注:你可以借此判斷是不是抵達生成器函數的末尾)
現在你也可以嘗試在循環中加入一個yield,看看會發生什么?
ES6生成器(Generators)深入
如果用專業術語描述,每當生成器執行yields語句,生成器的堆棧結構(本地變量、參數、臨時值、生成器內部當前的執行位置)被移出堆棧。然而,生成器對象保留了對這個堆棧結構的引用(備份),所以稍后調用.next()可以重新激活堆棧結構并且繼續執行。
值得特別一提的是,生成器不是線程,在支持線程的語言中,多段代碼可以同時運行,通通常導致競態條件和非確定性,不過同時也帶來不錯的性能。生成器則完全不同。當生成器運行時,它和調用者處于同一線程中,擁有確定的連續執行順序,永不并發,(即類似同步執行)。與系統線程不同的是,生成器只有在其函數體內標記為
yield
的點才會暫停。
現在,我們了解了生成器的原理,領略過生成器的運行、暫停恢復運行的不同狀態。那么,這些奇怪的功能究竟有何用處?
(3)生成器是迭代器!生成器與迭代器
上面我大概講了ES6的迭代器的概念和小例子,它是ES6中獨立的內建類,同時也是語言的一個擴展點,通過實現[Symbol.iterator]()
和.next()
兩個方法你就可以創建自定義迭代器。
注:記住
[Symbol.iterator]()
和.next()
兩個方法就可以創建自定義迭代器
生成器代替迭代器
實現一個接口不是一樁小事,我們一起實現一個迭代器。舉個例子,我們創建一個簡單的名為range
迭代器,它可以簡單地將兩個數字之間的所有數相加。在這里我們使用ES6的類的解決方案(如果不清楚語法細節,無須擔心,我也將在以后的文章中為你講解):
代碼:
// 實現一個迭代器:簡單地將兩個數字之間的所有數相加
// 使用ES6的類的解決方案
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
} else {
return {done: true, value: undefined};
}
}
}
// 返回一個新的迭代器,可以從start到stop計數。
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log("ES6的類" + value); // ES6的類0,ES6的類1,ES6的類2
}
這里的實現類似Java
或Swift
中的迭代器,不是很糟糕,但也不是完全沒有問題。我們很難說清這段代碼中是否有bug,這段代碼看起來完全不像我們試圖模仿的傳統for (;;)
循環,迭代器協議迫使我們拆解掉循環部分。此時此刻你對迭代器可能尚無感覺,他們用起來很好,但看起來有些難以實現和理解。
你大概不會為了使迭代器更易于構建從而建議我們為JS語言引入一個離奇古怪又野蠻的新型控制流結構,但是既然我們有生成器,并且包含[Symbol.iterator]()
和.next()
兩個方法就可以創建自定義迭代器,那么我們是否可以在這里應用生成器來解決問題呢?一起嘗試一下:
// 生成器解決:
var oldValue = 0
for (var value of range1(0, 3)) {
console.log("生成器" + value); // 生成器0,生成器1,生成器2
console.log(oldValue + value); // 0,1,3
oldValue = value;
}
function* range1(start, stop) {
for (var i = start; i < stop; i++)
yield i;
}
以上簡便代碼實現的生成器完全可以替代之前引入了一整個RangeIterator類的代碼的實現。可行的原因是:生成器是迭代器。因為所有的生成器都有內建.next()
和[Symbol.iterator]()
方法的實現。所以你只須編寫循環部分的行為。而不再需要寫[Symbol.iterator]()
和.next()
兩個方法來創建自定義迭代器。
生成器的最大效力
我們都非常討厭被迫用古怪語態寫一封很長的郵件,不借助生成器實現迭代器的過程與之類似,令人痛苦不堪。當你的語言不再簡練,說出的話就會變得難以理解。上面RangeIterator類的實現代碼很長并且非常奇怪,因為你需要在不借助循環語法的前提下為它添加循環功能的描述。所以生成器是最好的解決方案!
我們如何發揮作為迭代器的生成器所產生的最大效力?
- 使任意對象可迭代(上面關于迭代器的例子,為對象添加
[Symbol.iterator]()
和.next()
方法)。編寫生成器函數遍歷這個對象,運行時yield每一個值。然后將這個生成器函數作為這個對象的[Symbol.iterator]
方法。
- 簡化數組構建函數。假設你有一個函數,每次調用的時候返回一個數組結果。就像這樣:
// 拆分一維數組icons變為二維
// 普通實現
function splitIntoRows(icons, rowLength) {
var rows = [];
for (var i = 0; i < icons.length; i += rowLength) {
rows.push(icons.slice(i, i + rowLength));
}
return rows;
}
var icons = [1,2,3,4,5,6,7,8,9,0]
console.log(JSON.stringify(splitIntoRows(icons, 2))); // [[1,2],[3,4],[5,6],[7,8],[9,0]]
// 生成器創建
function* splitInto(icons, rowLength) {
for (var i = 0; i < icons.length; i += rowLength) {
yield icons.slice(i, i + rowLength);
}
}
var str = [1,2,3,4,5,6,7,8,9,0]
for (var value of splitInto(str, 2)) {
console.log("生成器創建數組" + value); // 生成器創建數組1,2 生成器創建數組3,4...生成器創建數組9,0
}
console.log(splitInto(str, 2).next()); // Object {value: Array[2], done: false}
行為上唯一的不同是,傳統寫法立即計算所有結果并返回一個數組類型的結果,使用生成器則返回一個迭代器,每次根據需要逐一地計算結果。
你一定會疑問,這可以用來做什么?
- 獲取異常尺寸的結果。你無法構建一個無限大的數組,但是你可以返回一個可以生成一個永無止境的序列的生成器,每次調用可以從中取任意數量的值。
- 重構復雜循環。你是否寫過又丑又大的函數?你是否愿意將其拆分為兩個更簡單的部分?現在,你的重構工具箱里有了新的利刃——生成器。當你面對一個復雜的循環時,你可以拆分出生成數據的代碼,將其轉換為獨立的生成器函數,然后使用
for (var data of myNewGenerator(args))
遍歷我們所需的數據。 - 構建與迭代相關的工具。ES6不提供用來過濾、映射以及針對任意可迭代數據集進行特殊操作的擴展庫。借助生成器,我們只須寫幾行代碼就可以實現類似的工具。
舉個例子,假設你需要一個等效于Array.prototype.filter
(filter()
方法使用指定的函數測試所有元素,并創建一個包含所有通過測試的元素的新數組。),并且支持DOM NodeLists
的方法,可以這樣寫: (Array.prototype.filter)
// 過濾數組中每一項對象的id屬性是數字
// 定義數組
var arr = [
{ id: 15 },
{ id: -1 },
{ id: 0 },
{ id: 3 },
{ id: 12.2 },
{ },
{ id: null },
{ id: NaN },
{ id: 'undefined' }
];
var invalidEntries = 0; // 用來記錄無效條目數
function isNumber(obj) { // 判斷是不是數字
return obj!== undefined && typeof(obj) === 'number' && !isNaN(obj);
}
function filterByID(item) { // 過濾id,是數字的返回true,否則返回false
if (isNumber(item.id)) {
return true;
}
invalidEntries++;
return false;
}
var arrByID = arr.filter(filterByID);
console.log('過濾后的數組\n', arrByID); // 過濾后的數組---[{ id: 15 }, { id: -1 }, { id: 0 }, { id: 3 }, { id: 12.2 }]
console.log('無效條目數 = ', invalidEntries); // 無效條目數 = 4
借助生成器可以非常輕松地實現自定義迭代器,記住,迭代器貫穿ES6的始終,它是數據和循環的新標準。
生成器和異步代碼及同步
異步API通常需要一個回調函數,這意味著你需要為每一次任務執行編寫額外的異步函數。異步API擁有錯誤處理規則,不支持異常處理。不同的API有不同的規則,大多數的錯誤規則是默認的;在有些API里,甚至連成功提示都是默認的。
生成器為你提供了避免異步代碼問題的新思路。
實驗性的
Q.async()
嘗試結合promises
使用生成器產生異步代碼的等效同步代碼。(async
函數就是Generator
函數的語法糖,async
函數很多人認為它是異步操作的終極解決方案。)舉個例子:
// 制造一些噪音的同步代碼。
function makeNoise() {
shake();
rattle();
roll();
}
// 制造一些噪音的異步代碼。
// 返回一個Promise對象
// 當我們制造完噪音的時候會變為resolved
function makeNoise_async() {
// 注意目前瀏覽器都只支持ES5,所以該代碼會在瀏覽器上報錯
return Q.async(function* () {
yield shake_async();
yield rattle_async();
yield roll_async();
});
}
// Q.async()嘗試結合promises
var eventualAdd = Q.async(function* (oneP, twoP) {
var one = yield oneP;
var two = yield twoP;
return one + two;
});
eventualAdd(eventualOne, eventualTwo).then(function (three) {
three === 3;
});
二者主要的區別是,異步版本必須在每次調用異步函數的地方添加yield關鍵字。
要玩Q.async()請記得引入<script src="q.js"></script>,并且在支持ES6轉換為ES5的編輯器中編程。這里附上相關鏈接:Q.async()和q.js,以及async 函數的含義和用法
在Q.async版本中添加一個類似if語句的判斷或try/catch塊,如同向同步版本中添加類似功能一樣簡單。與其它異步代碼編寫方法相比,這種方法更自然,不像是學一門新語言一樣辛苦。
當你已經看到這里,如果需要更詳細的,你可以試著閱讀來自James Long這個老外的更深入地講解生成器的文章。
生成器為我們提供了一個新的異步編程模型思路,這種方法更適合人類的大腦。此外,更好的語法或許會有幫助,ES7中有一個有關異步函數的提案,它基于promises和生成器構建,并從C#相似的特性中汲取了大量靈感。
如何應用這些瘋狂的新特性?
在服務器端,現在你可以在io.js
中使用ES6(在Node中你需要使用--harmony
這個命令行選項)。
在瀏覽器端,到目前為止只有Firefox 27+和Chrome 39+支持了ES6生成器。如果要在web端使用生成器,你需要使用Babel
或Traceur
來將你的ES6代碼轉譯為Web友好的ES5。
起初,JS中的生成器由Brendan Eich實現,他的設計參考了Python生成器,而此外Python生成器則受到Icon的啟發。他們早在2006年就在Firefox 2.0中移植了相關代碼。但是,標準化的道路崎嶇不平,相關語法和行為都在原先的基礎上有所改動。Firefox和Chrome中的ES6生成器都是由編譯器hacker Andy Wingo實現的。
結語
生成器還有更多未提及的特性,例如:.throw()
和.return()
方法、可選參數.next()
、yield*
表達式語法。由于行文過長,估計各位觀眾老爺們已然疲乏,剩下的干貨擇機為大家獻上。
由于我們接連搬了ES6的兩座大山:迭代器和生成器,以及for-of,下次再給大家分享更好的ES6特性。