生成器是 ECMAScript 6 新增的一個極為靈活的結構,擁有一個函數塊內暫停和恢復代碼執行的能力。
生成器函數提供了一個強大的選擇:它允許你定義一個包含自有迭代算法的函數,同時它可以自動維護自己的狀態。
使用 function*語法定義生成器函數,而且 * 不受兩側空格的影響。只要是可以定義函數的地方,就可以定義生成器。
function* generator() {}
注意:箭頭函數不能用來定義生成器函數。
方法調用生成器函數會產生一個符合可迭代協議和迭代器協議的生成器對象。生成器對象一開始處於暫停執行的狀態。與迭代器相似,生成器也實現了 Iterator 接口。
Generator.prototype.next()Generator 對象具有 next() 方法,調用這個方法會讓生成器開始或恢復執行,而且返回的是一個 IteratorResult 對象,具有 done 屬性和 value 屬性。默認情況下,返回的值為 { done: true, value: undefined }。
function* generator() {
return 'sample';
}
const v = generator();
// 默認的迭代器是自引用的
console.log(v); // generator {<suspended>}
console.log(generator()[Symbol.iterator]()); // generator {<suspended>}
console.log(v.next()); // { value: 'sample', done: true }
生成器還可以通過 next(value) 方法向內部傳參,並且這個參數會變成 yield 的結果。
function* generator() {
let v = yield 10;
yield v;
}
let v = generator();
console.log(v.next(1)); // { value: 10, done: false }
console.log(v.next(2)); // { value: 2, done: false }
第一次調用 next() 傳入的值不會被使用,因為這一次調用時為了開始執行生成器函數,它會執行並返回第一個 yield 10 的結果。第二次 next() 調用,獲得了 2 作為結果let v = 2 並返回 yield v。
Generator.prototype.return()Generator 提供的 return() 方法返回給定的值並結束生成器。因此可以使用 return() 方法提前終止生成器。
function* generator() {
yield 'sample';
yield ;
yield 'example';
return 'instance';
}
const v1 = generator();
console.log(v1); // generator {<suspended>}
console.log(v1.return("quit")); // { done: true, value: "quit" }
console.log(v1); // generator {<closed>}
當 return() 方法沒有提供參數,則返回對象的 value 屬性的值為 undefined。
當使用 return() 方法關閉了生成器後就無法恢復,後續調用 next() 方法返回的值為 {done: true, value: undefined}。
console.log(v1.next()); // {done: true, value: undefined}
for-of 循環等內置語言結構會忽略狀態為 done: true 的 IteratorObject 內部返回的值。
function* generator() {
yield 'sample';
yield ;
yield 'example';
return 'instance';
}
let v1 = generator();
for (const x of v1) {
if (x === 'example') {
v1.return("quit");
}
console.log(x);
}
// sample
// undefined
// example
生成器還提供了 throw() 方法用來向生成器中注入一個錯誤。如果內部未處理該錯誤,那麼生成器就會關閉。
function* generator() {
yield 'sample';
yield ;
yield 'example';
return 'instance';
}
const v = generator();
console.log(v); // // generator {<suspended>}
try {
v.throw(new Error("Throw Error"));
} catch (e) {
console.log(e); // Error: Throw Error
}
console.log(v); // generator {<closed>}
但是生成器函數內部處理了這個錯誤,那麼生成器就不會關閉,還可以恢復執行。錯誤處理會跳過對應的 yield,因此在這個例子中會跳過一個值。如下所示:
function* generator() {
for (const x of ["sample", "example", "instance"]) {
try {
yield x;
} catch (e) {
console.log("Error caught!");
}
}
}
const v = generator();
console.log(v.next()); // { value: "sample", done: false}
v.throw(new Error("Throw Error")); //
console.log(v); // generator {<suspended>}
console.log(v.next); // { value: "instance", done: false}
在這裡,向生成器內部注入一個錯誤,該錯誤會被 yield 關鍵字拋出,並且在生成器內部的 try-catch 語句塊中處理了該錯誤。此時,生成器函數還是會繼續執行,但下次調用 next() 方法就不會產生 example 值,而是產生 instance 值。
注意:如果生成器對象還沒有開始執行,那麼調用 throw() 拋出的錯誤不會在函數內部被捕獲,因為這相當於在函數塊外部拋出了錯誤。
yieldECMAScript 6 提供了 yield 關鍵字用來讓生成器函數暫停,並且函數作用域的狀態會保留。生成器對象會通過調用 next() 方法讓生成器函數恢復執行。yield 省略表達式會返回 undefined:
function* generator() {
yield 'sample';
yield ;
yield 'example';
return 'instance';
}
let v1 = generator();
console.log(v1.next()); // { value: 'sample', done: false }
console.log(v1.next()); // { value: undefined, done: false }
console.log(v1.next()); // { value: 'example', done: false }
console.log(v1.next()); // { value: 'instance', done: true }
console.log(v1.next()); // { value: undefined, done: true }
生成器對象可當成可迭代對象,可以使用 for...of 循環。
function* generator() {
for (const x of ["sample", "example", "instance"]) {
yield x;
}
}
for (const x of generator()) {
console.log(x);
}
// sample
// example
// instance
注意:yield 關鍵字只能在生成器函數內部使用且必須位於生成器函數定義中,出現在嵌套的非生成器函數中會拋出錯誤。
還可以使用 yield* 語法增強 yield 的行為,用於委託給另一個 generator 或可迭代的對象。
function* generator() {
for (const v of [1, 2, 3]) {
yield v;
}
}
等價於
function* generator() {
yield* [1, 2, 3];
}
等價於
function* generator1() {
yield 1;
yield 2;
}
function* generator2() {
yield* generator1();
yield 3;
}
注意,yield 與 * 兩側的空格不影響其行為。
由於 yield* 可以調用另外一個生成器,所以通過 yield* 可以實現遞歸調用。
function* recs(n) {
if (n > 1) {
yield* f(n-1);
}
yield n;
}
for (const x of recs(3)) {
console.log(x);
}
// 1
// 2
// 3
使用遞歸生成器結構和 yield* 可以優雅地表達遞歸算法。
小結生成器是一種特殊的函數,調用之後會返回一個生成器對象。生成器對象實現了 Iterable 接口,因此應用場景與迭代器一樣。生成器支持 yield 關鍵字,用於暫停執行生成器函數,與 next() 方法搭配產生一系列值。yield* 表達式可以在生成器中調用其它生成器。