Generator函數是ES6提供的一種異步編程解決方案,它語法行爲與傳統函數不一樣,咱們先來看一個使用Generator書寫的Fibonacci函數的示例:es6
function* fibonacci(n) {
let current = 0;
let next = 1;
while (n-- > 0) {
yield current;
[current, next] = [next, next + current];
}
}
複製代碼
費波那契數列由0和1開始,以後的費波那契係數就是由以前的兩數相加而得出。而咱們上面的生成器參數n即表明須要計算到第幾個序列:編程
const test = fibonacci(3);
console.log(test.next()); // {value: 0, done: false}
console.log(test.next()); // {value: 1, done: false}
console.log(test.next()); // {value: 1, done: false}
console.log(test.next()); // {value: undefined, done: true}
for (item of fibonacci(3)) {
console.log(item) // 0, 1, 1
}
複製代碼
經過以上實踐,能夠簡單總結出生成器的幾個特色:數組
{value: any, done: boolean}
。for ... of
執行。Generator生成器與普通函數相比具備2個特徵:bash
*
。yield
表達式。當在調用生成器時,它不容許使用new關鍵字,也不會當即執行,而是返回一個Iterator
對象,容許經過遍歷器、for...of
、解構語法等表達式執行。數據結構
每個 yield 表達式,會使 Generator 生成一種新的狀態,並轉交控制權給外部函數,此時咱們須要調用遍歷器對象的next
方法,才能使 Generator 繼續執行。須要注意的是,Generator函數內部若是不存在yield
表達式,它也不會當即執行,而是須要手動使用next
方法觸發:異步
function* test() { console.log('hello') }
const fn = test(); // 沒有任何輸出
fn.next(); // hello
複製代碼
Generator函數結束的標誌爲 return,return 返回的值也會做爲next方法返回對象的value,而此時 done 屬性爲:true(若是函數體內無 return 關鍵字,則會執行到函數結束,默認返回值爲undefined)。異步編程
yield表達式用於定義Generator不一樣的內部狀態,它同時做爲函數暫停的標誌,將執行權交給外部的其餘函數,並將 yield 關鍵字緊鄰的表達式做爲接下來遍歷器的next()
方法返回的對象的value鍵值。外部函數在調用了next()
方法之後,Generator才得以恢復執行:函數
function* test() {
yield 'hello'
yield 'world'
return '!'
}
const executer = test();
executer.next(); // {value: "hello", done: false}
executer.next(); // {value: "world", done: false}
executer.next(); // {value: "!", done: true}
for (item of test()) { console.log(item) } // hello world
複製代碼
return與yield都能使函數中止執行,並將後面的表達式的值做爲返回對象value傳遞出去,區別在於return是函數結束的標識,不具有屢次執行的能力,返回的值也不能做爲迭代對象使用(迭代器若是判斷 done 標識爲true,則會忽略該值)。學習
那咱們能夠在生成器中調用生成器嗎?最開始嘗試時可能會這樣寫:ui
function* hello() {
yield 'hello'
yield world()
}
function* world() {
yield 'world'
}
for (item of hello()) { console.log(item) }
// hello
// world {<suspended>}
複製代碼
第二個迭代器的值返回的是一個新的Generator,它按照原樣返回了,並無按照咱們預想中執行。爲了在一個Generator函數裏執行另外一個Generator,此時就須要使用yield*
表達式:
function* hello() {
yield 'hello'
yield* world()
}
function* world() {
yield 'world'
}
for (item of hello()) { console.log(item) }
// hello
// world
複製代碼
yield*
語句後面能接生成器對象或是實現了Iterator接口的值(字符串對象、數組對象等),它的做用就像是將生成器對象進行了for...of
遍歷,將每個遍歷到的對象傳遞到當前的生成器中執行yield,舉一個示例:
function* example() {
yield 'hello';
yield* ['world'];
yield* test();
yield* '??';
}
function* test() {
yield '!';
}
// example函數等同於
function* example() {
yield 'hello';
for (item of ['world']) {
yield item;
}
yield '!'
for (item of '??') {
yield item;
}
}
複製代碼
經過以上對Generator函數的介紹,咱們對Iterator有了一個初步的瞭解,Generator函數在運行後,會生成一個遍歷器對象,再由for...of
語法或是解構函數對Iterator進行消費。其實Iterator不只僅應用於Generator,它其實仍是一種通用的接口規範,爲不一樣的數據結構提供統一的訪問機制。
ES6規定,Iterator接口部署在對象的Symbol.iterator
上,凡是實現了這一屬性的對象都認爲是可遍歷的(iterable),原生具有Iterator接口的數據結構有:
所以對一個字符串來說,咱們能夠手動獲取到它的遍歷器對象,並進行循環打印每個字符:
const strs = 'hello world'
for (str of strs[Symbol.iterator]()) { console.log(str) }
複製代碼
除以上原生實現了Iterable數據結構之外,咱們還能夠本身定義任意對象的Symbol.iterator
屬性方法,從而實現Iterable特性,該屬性方法具有如下特徵:
Iterator Object
Iterator Object
中,存在一個next()
方法,該函數老是返回一個{value: any, done: boolean}
對象,done
默認值爲false
,value
默認值爲undefined
:value
能夠是任意值done
爲true
,表示迭代器已經執行到序列的末尾;done
爲false
表示迭代器還能夠繼續執行next()
方法並返回下一個序列對象實現Iterator接口有不少種方法,不論你的數據結構爲類仍是對象,咱們只要保證[Symbol.iterator]屬性方法及其返回的數據規範便可,如下爲自定義迭代器的示例:
// 使用生成器的方式,推薦使用
const obj = {
[Symbol.iterator] = function* () {
yield 'hello';
yield 'world';
}
}
// 使用純函數的方式定義返回對象及next方法
const obj = {
[Symbol.iterator]: () => {
const items = ['hello', 'world'];
let nextIndex = 0;
return {
next() {
return nextIndex < items.length
? { value: items[nextIndex++] }
: { done: true };
}
};
}
};
for (item of obj) { console.log(item) }
複製代碼
這三種方法都屬於Generator的原型方法,經過其對象進行調用,目的是讓Generator恢復執行,並使用不一樣語句替換當前yield標誌所在的表達式:
next(value)
,將表達式替換爲value,next函數主要用於向生成器內部傳遞值,從而改變生成器的狀態。throw(error)
,將表達式替換爲throw(error),throw方法會將Error對象交由生成器內部處理,若生成器沒法處理則會又將錯誤拋出來,此時會中斷生成器的執行。return(value)
,將表達式替換爲return value,return方法用於中斷生成器的執行。若要一一舉例,篇幅可能會很是長,所以在這裏舉一個包含這三種語句的示例:
function* test() {
const flag = yield 'does anybody here';
if (flag) {
try {
yield 'could you sing for me?';
} catch (e) {
if (e.message === 'sorry!') {
yield 'I can teach you';
} else {
throw e
}
}
yield 'maybe next time';
}
}
const executer = test();
executer.next(); // {value: 'does anybody here?'}
executer.next(true); // { value: 'could you sing for me ?' }
executer.throw(new Error('sorry!')); // {value: 'i will teach you'}
executer.return('thank you'); // {value: "thank you", done: true}
複製代碼
函數有幾點須要解釋: