ES6做爲新一代JavaScript標準,即將與廣大前端開發者見面。爲了讓你們對ES6的諸多新特性有更深刻的瞭解,Mozilla Web開發者博客推出了《ES6 In Depth》系列文章。CSDN已獲受權,將持續對該系列進行翻譯,組織成【探祕ES6】系列專欄,供你們學習借鑑。本文爲該系列的第三篇。前端
ES6生成器介紹es6
什麼是生成器呢?編程
請先看看如下代碼。數組
function* quips(name) { yield "hello " + name + "!"; yield "i hope you are enjoying the blog posts"; if (name.startsWith("X")) { yield "it's cool how your name starts with X, " + name; } yield "see you later!"; }
這是一段有關湯姆貓(talking cat)的代碼。它看上去像是一個函數,是嗎?在ES6中,它的名字是生成器函數,其與普通函數有不少類似的地方。但有兩點不一樣:
異步
生成器函數以function*開頭;async
在生成器函數中,yield是一個關鍵字,如同return。yield能夠屢次使用,做用是中斷生成器,ide
而在須要的時候能夠恢復生成器的執行。異步編程
因此生成器函數最大的特色是能夠中斷本身,但普通函數不能夠。函數
生成器的做用工具
當使用quips()生成器函數時會出現什麼狀況呢?
> var iter = quips("jorendorff"); [object Generator] > iter.next() { value: "hello jorendorff!", done: false } > iter.next() { value: "i hope you are enjoying the blog posts", done: false } > iter.next() { value: "see you later!", done: false } > iter.next() { value: undefined, done: true }
對於普通quips(),它會立刻執行直到出現返回或異常拋出等狀況。在生成器函數中,調用方式是相似的:quips("jorendorff"),可是它不會立刻執行。取而代之的是,它會返回一個已暫停的生成器對象(如上述代碼的iter)。你能夠把生成器對象當作是一個被暫停的函數調用。要特別說明的是生成器對象在生成器函數開始時被凍結,即第一行代碼執行以前。
每當調用生成器對象的.next()方法時,函數恢復運行直至遇到下一個yield表達式,其做用是用於迭代。所以iter.next()的目的是爲了返回不一樣的字符串。在最後的iter.next()中,使用done:true表示結束。到達函數末端意味着返回的結果是undefined,因此代碼片斷中使用value: undefined結尾。
從技術角度來看,每當生成器執行yield操做時,它的堆棧幀包括本地變量、參數、臨時值等都會從堆中被移出。可是生成器對象會保留(拷貝)對該幀的引用,因此.next()能夠從新激活它而後繼續執行。這裏特別要說明的是,生成器不是線程。當一個生成器執行時,它與其調用者都處於同一個線程,是按次序執行而不是並行運行。
可見生成器的做用是暫停自己的運行,而後恢復並繼續執行,那麼這究竟有何用處呢?
生成器就是迭代器
ES6迭代器不是內建的,可嘗試經過使用[Symbol.iterator]()和.next()來進行建立。可是這種相似接口的作法不是最簡便的方法。請看下面一個range迭代器例子,它的做用相似於C的for(;;)循環。
// This should "ding" three times for (var value of range(0, 3)) { alert("Ding! at floor #" + value); }
具體的實現代碼:
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}; } } } // Return a new iterator that counts up from 'start' to 'stop'. function range(start, stop) { return new RangeIterator(start, stop); }
可見迭代器的生成並非件簡單的事情。那麼若是採用生成器來實現,應該如何編寫呢?
function* range(start, stop) { for (var i = start; i < stop; i++) yield i; }
對比是很明顯的,上述代碼僅需4行代碼就完成了相同的功能,由於生成器就是迭代器。全部生成器都內建了對.next()和[Symbol.iterator]()的支持,你只須要負責循環的實現就能夠了。
除此之外,做爲迭代器使用的生成器還能夠實現哪些功能呢?
使任何對象可迭代。方法是編寫一個生成器函數,而後對每一個值進行迭代。而後使用對象的[Symbol.iterator]方法與生成器函數進行綁定。
簡化數組功能。若是要實現以數組形式返回函數結果,能夠這樣寫:
// Divide the one-dimensional array 'icons' // into arrays of length '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); } }
後者與前者的區別是否是一次就計算全部結果並返回一個數組,而是先返回一個迭代器,而後按次序按需進行計算。
返回特殊長度數組。數組是有長度限制的,可是透過生成器迭代特性,能夠產生無限的序列。
重構複合循環。當編寫一個複雜的循環時,能夠提出產生數據的部分,把它改寫爲一個獨立的生成器函數。例如(var data of myNewGenerator(args))。
進行迭代運算的配套工具。ES6並無提供有關篩選、映射、迭代數據集操做的擴展庫。可是藉助生成器,咱們能夠圍繞它來簡單地建立相關工具。
例如,要在DOM節點上實現與Array.prototype.filter相似的功能,能夠這樣編寫:
function* filter(test, iterable) { for (var item of iterable) { if (test(item)) yield item; } }
可見,生成器真的妙趣橫生。藉助生成器能夠方便地實現定製的迭代操做,而迭代是ES6中貫穿始終的新的數據和循環標準。
生成器和異步代碼
下面是我以前寫過的一段代碼:
}; }) }); }); }); });
可能你的代碼中也會看到相似的片斷。異步APIs一般須要回調函數,這意味着每當作一些事情時就寫一個匿名函數。因此若是你須要作三件事情,編寫三行代碼可不行,而是須要三段一致的代碼。
請看我寫過的一段代碼:
}).on('close', function () { done(undefined, undefined); }).on('error', function (error) { done(error); });
異步APIs提供的是錯誤處理而非異常處理,不一樣APIs有不一樣的處理方法。而大多數錯誤定義是默認的,因此進行異步編程時須要花必定時間去了解。生成器則提供了新的處理方式。
Q.async()是一個實驗性的相似於同步代碼的異步代碼生成方法,請看代碼:
// Synchronous code to make some noise. function makeNoise() { shake(); rattle(); roll(); } // Asynchronous code to make some noise. // Returns a Promise object that becomes resolved // when we're done making noise. function makeNoise_async() { return Q.async(function* () { yield shake_async(); yield rattle_async(); yield roll_async(); }); }
二者的主要區別是異步代碼必須使用yield關鍵字來執行異步函數。所以生成器爲新的異步編程模型帶來了新的思路,更符合人的思惟習慣。
寫在最後
限於篇幅,生成器還有兩個方法留待後續講解,.throw()和.return()。多看官方文檔多動手練習,你會發現ES6更多精彩。(譯者:伍昆 責編:陳秋歌)
本譯文遵循Creative Commons Attribution Share-Alike License v3.0