咱們從一個示例開始:python
function* quips(name) {
yield "你好 " + name + "!";
yield "但願你能喜歡這篇介紹ES6的譯文";
if (name.startsWith("X")) {
yield "你的名字 " + name + " 首字母是X,這很酷!";
}
yield "咱們下次再見!";
}git
這段代碼看起來很像一個函數,咱們稱之爲生成器函數,它與普通函數有不少共同點,可是兩者有以下區別:github
這就是普通函數和生成器函數之間最大的區別,普通函數不能自暫停,生成器函數能夠。web
當你調用quips()生成器函數時發生了什麼?編程
> var iter = quips("jorendorff");
[object Generator]
> iter.next()
{ value: "你好 jorendorff!", done: false }
> iter.next()
{ value: "但願你能喜歡這篇介紹ES6的譯文", done: false }
> iter.next()
{ value: "咱們下次再見!", done: false }
> iter.next()
{ value: undefined, done: true }數組
生成器調用看起來很是相似:quips("jorendorff")。可是,當你調用一個生成器時,它並不是當即執行,而是返回一個已暫停的生成器對象(上述實例代碼中的iter)。你可將這個生成器對象視爲一次函數調用,只不過當即凍結了,它剛好在生成器函數的最頂端的第一行代碼以前凍結了。promise
每當你調用生成器對象的.next()方法時,函數調用將其自身解凍並一直運行到下一個yield表達式,再次暫停。瀏覽器
這也是在上述代碼中咱們每次都調用iter.next()的緣由,咱們得到了quips()函數體中yield表達式生成的不一樣的字符串值。服務器
調用最後一個iter.next()時,咱們最終抵達生成器函數的末尾,因此返回結果中done的值爲true。抵達函數的末尾意味着沒有返回值,因此返回結果中value的值爲undefined。babel
若是用專業術語描述,每當生成器執行yields語句,生成器的堆棧結構(本地變量、參數、臨時值、生成器內部當前的執行位置)被移出堆棧。然而,生成器對象保留了對這個堆棧結構的引用(備份),因此稍後調用.next()能夠從新激活堆棧結構而且繼續執行。
值得特別一提的是,生成器不是線程,在支持線程的語言中,多段代碼能夠同時運行,統統常致使競態條件和非肯定性,不過同時也帶來不錯的性能。生成器則徹底不一樣。當生成器運行時,它和調用者處於同一線程中,擁有肯定的連續執行順序,永不併發。與系統線程不一樣的是,生成器只有在其函數體內標記爲yield的點纔會暫停。
如今,咱們瞭解了生成器的原理,領略過生成器的運行、暫停恢復運行的不一樣狀態。那麼,這些奇怪的功能究竟有何用處?
實現一個接口不是一樁小事,咱們一塊兒實現一個迭代器。舉個例子,咱們建立一個簡單的range迭代器,它能夠簡單地將兩個數字之間的全部數相加。首先是傳統C的for(;;)循環:
// 應該彈出三次 "ding"
for (var value of range(0, 3)) {
alert("Ding! at floor #" + value);
}
使用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);
}
你大概不會爲了使迭代器更易於構建從而建議咱們爲JS語言引入一個離奇古怪又野蠻的新型控制流結構,可是既然咱們有生成器,是否能夠在這裏應用它們呢?一塊兒嘗試一下:
function* range(start, stop) {
for (var i = start; i < stop; i++)
yield i;
}
以上4行代碼實現的生成器徹底能夠替代以前引入了一整個RangeIterator類的23行代碼的實現。可行的緣由是:生成器是迭代器。全部的生成器都有內建.next()和[Symbol.iterator]()方法的實現。你只須編寫循環部分的行爲。
咱們都很是討厭被迫用被動語態寫一封很長的郵件,不借助生成器實現迭代器的過程與之相似,使人痛苦不堪。當你的語言再也不簡練,說出的話就會變得難以理解。RangeIterator的實現代碼很長而且很是奇怪,由於你須要在不借助循環語法的前提下爲它添加循環功能的描述。因此生成器是最好的解決方案!
咱們如何發揮做爲迭代器的生成器所產生的最大效力?
l 使任意對象可迭代。編寫生成器函數遍歷這個對象,運行時yield每個值。而後將這個生成器函數做爲這個對象的[Symbol.iterator]方法。
l 簡化數組構建函數。假設你有一個函數,每次調用的時候返回一個數組結果,就像這樣:
// 拆分一維數組icons
// 根據長度rowLength
function splitIntoRows(icons, rowLength) {
var rows = [];
for (var i = 0; i < icons.length; i += rowLength) {
rows.push(icons.slice(i, i + rowLength));
}
return rows;
}
使用生成器建立的代碼相對較短:
function* splitIntoRows(icons, rowLength) {
for (var i = 0; i < icons.length; i += rowLength) {
yield icons.slice(i, i + rowLength);
}
}
行爲上惟一的不一樣是,傳統寫法當即計算全部結果並返回一個數組類型的結果,使用生成器則返回一個迭代器,每次根據須要逐一地計算結果。
舉個例子,假設你須要一個等效於Array.prototype.filter而且支持DOM NodeLists的方法,能夠這樣寫:
function* filter(test, iterable) {
for (var item of iterable) {
if (test(item))
yield item;
}
}
你看,生成器魔力四射!藉助它們的力量能夠很是輕鬆地實現自定義迭代器,記住,迭代器貫穿ES6的始終,它是數據和循環的新標準。
異步API擁有錯誤處理規則,不支持異常處理。不一樣的API有不一樣的規則,大多數的錯誤規則是默認的;在有些API裏,甚至連成功提示都是默認的。
這些是到目前爲止咱們爲異步編程所付出的代價,咱們正慢慢開始接受異步代碼不如等效同步代碼美觀又簡潔的這個事實。
生成器爲你提供了避免以上問題的新思路。
實驗性的Q.async()嘗試結合promises使用生成器產生異步代碼的等效同步代碼。舉個例子:
// 製造一些噪音的同步代碼。
function makeNoise() {
shake();
rattle();
roll();
}
// 製造一些噪音的異步代碼。
// 返回一個Promise對象
// 當咱們製造完噪音的時候會變爲resolved
function makeNoise_async() {
return Q.async(function* () {
yield shake_async();
yield rattle_async();
yield roll_async();
});
}
兩者主要的區別是,異步版本必須在每次調用異步函數的地方添加yield關鍵字。
在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*表達式語法。因爲行文過長,估計觀衆老爺們已然疲乏,咱們應該學習一下生成器,暫時yield在這裏,剩下的乾貨擇機爲你們獻上。