根據ES6制訂的標準自定義迭代器實現起來比較複雜,所以ES6又引入了生成器的概念,生成器(Generator)是一個能直接建立並返回迭代器的特殊函數,可將其賦給可迭代對象的Symbol.iterator屬性。與普通函數不一樣,生成器不只能夠暫停函數內部的執行(即維護內部的狀態),在聲明時還須要包含一個星號(*),而且擁有next()、return()和throw()三個迭代器方法。php
生成器在聲明時,須要把星號加到function關鍵字與函數名之間,但ES6沒有規定星號兩邊是否須要空格,所以下面四種寫法都是容許的,本篇將採用第一種寫法。html
function* generator() {} function*generator() {} function *generator() {} function * generator() {}
生成器也能經過函數表達式建立,以下代碼所示。注意,不能用箭頭函數建立生成器。編程
var iterator = function* () {};
生成器雖然不能做爲構造函數使用,但能夠是對象的一個方法,而且還支持第5篇提到的簡潔方式的寫法,以下所示。json
var obj = { *generator() {} };
生成器之因此能在其內部實現分批執行,還要多虧ES6新增的yield關鍵字。這個關鍵字可標記暫停位置,具體使用可參考下面的代碼。數組
function* generator() { var count = 0; while (count < 2) yield count++; return count; } var iterator = generator();
雖然生成器的調用方式和普通函數相同,但它不會立刻返回函數的結果(即不能馬上執行)。而是先返回一個它所生成的迭代器,而後再調用其next()方法恢復內部語句的執行(以下代碼所示),直至遇到yield關鍵字,再次暫停,如此反覆,一直持續到函數的末尾或碰到return語句才終止這套循環操做。異步
iterator.next(); //{value: 0, done: false} iterator.next(); //{value: 1, done: false} iterator.next(); //{value: 2, done: true}
1)yield表達式異步編程
yield關鍵字的後面能夠跟一個表達式,例如代碼中的count++。生成器的next()方法可以返回一個IteratorResult對象,其done屬性用於判斷生成器是否執行完畢,便是否還有yield表達式。關於IteratorResult兩個屬性的值,須要分狀況說明,具體以下所列。函數
(1)當生成器還在執行時,value的值可經過計算yield表達式獲得,done的值爲false。fetch
(2)當生成器執行完畢時,value的值是undefined,done的值爲true。this
(3)當遇到return語句時,value的值就是return後面跟的值,done的值爲true。
要想遍歷生成器,除了藉助next()方法以外,還可使用for-of循環。但要注意,遍歷到的是yield表達式的計算結果,以下所示。
/******************** 0 1 ********************/ for(var step of iterator) { console.log(step); }
2)優先級和結合性
由於yield能夠單獨使用(例如x=yield),因此它並非一個運算符。雖然如此,但它仍是包含優先級和結合性的概念。yield的優先級很低,僅比擴展運算符和逗號高,若是要提早計算,能夠像下面這樣用一對圓括號包裹。
1 + (yield 2);
yield的結合性與等號同樣,也是從右往左,例如yield yield 1至關於yield(yield 1)。另外,yield有一個很重要的限制,就是它只能存在於生成器內,在其它位置出現都會有異常,包括生成器中的子函數內,以下所示。
function* error() { function inner() { yield 1; } }
1)next()
本節開篇的時候曾提到過生成器包含三個迭代器方法,接下來將圍繞這三個方法展開講解。首先介紹的是next()方法,它能接收一個參數,而這個參數會成爲上一個yield表達式的返回值。如下面的代碼爲例,calculate()函數包含兩個yield表達式,在建立生成器後,調用了兩次next()方法,第一次沒有傳參,第二次傳入的數字10被賦給了x變量。
function* calculate() { let x = yield 1; let y = yield x + 2; return y; } var iterator = calculate(); iterator.next(); //{value: 1, done: false} iterator.next(10); //{value: 12, done: false}
注意,第一次調用next()方法時,即便傳進了參數,這個參數也會被忽略,由於此時還不存在上一個yield表達式。
2)return()
接下來介紹的是return()方法,它能提早終止當前生成器,相似於在函數體內立刻執行return語句。下面沿用上一個示例,將函數名改爲stop,第二次調用的方法改爲return()。
function* stop() { let x = yield 1; let y = yield x + 2; return y; } var iterator = stop(); iterator.next(); //{value: 1, done: false} iterator.return(10); //{value: 10, done: true} iterator.next(); //{value: undefined, done: true}
return()方法也能接收一個參數,而從上面的調用結果中能夠得知,這個參數至關於return運算符後面跟的值,以下所示。
function* stop() { let x = yield 1; return 10; }
3)throw()
最後介紹的是throw()方法,它能強制生成器拋出一個錯誤。此方法也有一個參數,但這個參數只能被try-catch語句中的catch部分接收。下面用一個例子演示throw()方法的具體使用。
function* especial() { var count = 1; try { yield count; } catch (e) { count = 2; console.log(e); //"inner" } yield count + 3; } var iterator = especial(); iterator.next(); //{value: 1, done: false} try { iterator.throw("inner"); //{value: 5, done: false} iterator.next(); //{value: undefined, done: true} iterator.throw("outer"); } catch (e) { console.log(e); //"outer" }
在especial生成器的內部和外部各有一條try-catch語句。第一次調用throw()方法,在生成器內部先捕獲拋出的錯誤,再把傳入的字符串「inner」賦給catch的e參數,接着執行yield count + 3,最後返回一個計算過的IteratorResult對象。第二次調用throw()方法,因爲生成器已執行完畢,所以只能在外部將錯誤捕獲。
在yield關鍵字後面跟一個星號(兩邊的空格可選),就能將執行權委託給另外一個生成器或可迭代對象。如下面代碼爲例,在delegation生成器中,有兩個yield*表達式,第一個跟的是數組,第二個跟的是generator生成器(至關於將兩個生成器合併)。
function* generator() { var count = 0; while (count < 2) yield count++; return count; } function* delegation() { yield* ["a", "b"]; var result = yield* generator(); console.log(result); //2 } var iterator = delegation(); iterator.next(); //{value: "a", done: false} iterator.next(); //{value: "b", done: false} iterator.next(); //{value: 0, done: false} iterator.next(); //{value: 1, done: false} iterator.next(); //{value: undefined, done: true}
從上面的遍歷結果中可知,delegation生成器先訪問數組的每一個元素,再計算generator生成器中的yield表達式,並將其返回值賦給了result變量。
在ES6以前,要實現異步編程,最經常使用的方法是用回調函數,例如捕獲Ajax通訊中的響應內容,以下所示。
function fetch(callback) { $.getJSON("server.php", {}, function(json) { callback.call(this, json); }); } function asyn() { fetch1(function(json) { console.log(json); //{code: 200, msg: "操做成功"} }); } asyn();
fetch()函數調用了jQuery中能發起Ajax請求的getJSON()方法,在其載入成功時的回調函數內間接調用了callback參數(即傳遞進來的回調函數),其參數就是響應內容。
接下來將asyn()變爲生成器,並在其內部添加yield表達式,而後在getJSON()的回調函數中調用生成器的next()方法,並將響應內容做爲參數傳入。
function fetch() { $.getJSON("server.php", {}, function(json) { gen.next(json); }); } function* asyn() { var result = yield fetch(); console.log(result); //{code: 200, msg: "操做成功"} } var gen = asyn(); gen.next();
經過上面的代碼可知,生成器能用同步的方式實現異步編程,從而有效避免了層層嵌套的回調金字塔。