生成器是 ECMAScript 6 新增的一個極為靈活的結(jié)構(gòu),擁有一個函數(shù)塊內(nèi)暫停和恢復代碼執(zhí)行的能力叼旋。
生成器函數(shù)提供了一個強大的選擇:它允許你定義一個包含自有迭代算法的函數(shù),同時它可以自動維護自己的狀態(tài)。
使用 function*
語法定義生成器函數(shù),而且 *
不受兩側(cè)空格的影響辜窑。只要是可以定義函數(shù)的地方,就可以定義生成器寨躁。
function* generator1() {}
<small>注意:箭頭函數(shù)不能用來定義生成器函數(shù)穆碎。</small>
方法
調(diào)用生成器函數(shù)會產(chǎn)生一個符合可迭代協(xié)議和迭代器協(xié)議的生成器對象。生成器對象一開始處于暫停執(zhí)行的狀態(tài)职恳。與迭代器相似惨远,生成器也實現(xiàn)了 Iterator
接口。
Generator.prototype.next()
Generator
對象具有 next()
方法话肖,調(diào)用這個方法會讓生成器開始或恢復執(zhí)行,而且返回的是一個 IteratorResult
對象葡幸,具有 done
屬性和 value
屬性最筒。默認情況下,返回的值為 { done: true, value: undefined }
蔚叨。
function* generator() {
return 'sample';
}
const v1 = generator();
// 默認的迭代器是自引用的
console.log(v1); // generator {<suspended>}
console.log(generator()[Symbol.iterator]()); // generator {<suspended>}
console.log(v1.next()); // { value: 'sample', done: true }
生成器還可以通過 next(value)
方法向內(nèi)部傳參床蜘,并且這個參數(shù)會變成 yield
的結(jié)果辙培。
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 }
第一次調(diào)用 next()
傳入的值不會被使用,因為這一次調(diào)用時為了開始執(zhí)行生成器函數(shù)邢锯,它會執(zhí)行并返回第一個 yield 10
的結(jié)果扬蕊。第二次 next()
調(diào)用,獲得了 2
作為結(jié)果let v = 2
并返回 yield v
丹擎。
Generator.prototype.return()
Generator
提供的 return()
方法返回給定的值并結(jié)束生成器尾抑。因此可以使用 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()
方法沒有提供參數(shù)蒂培,則返回對象的 value
屬性的值為 undefined
再愈。
當使用 return()
方法關閉了生成器后就無法恢復,后續(xù)調(diào)用 next()
方法返回的值為 {done: true, value: undefined}
护戳。
console.log(v1.next()); // {done: true, value: undefined}
for-of
循環(huán)等內(nèi)置語言結(jié)構(gòu)會忽略狀態(tài)為 done: true
的 IteratorObject
內(nèi)部返回的值翎冲。
function* generator() {
yield 'sample';
yield ;
yield 'example';
return 'instance';
}
let v1 = generator1();
for (const x of v1) {
if (x === 'example') {
v1.return("quit");
}
console.log(x);
}
// sample
// undefined
// example
Generator.prototype.throw()
生成器還提供了 throw()
方法用來向生成器中注入一個錯誤。如果內(nèi)部未處理該錯誤媳荒,那么生成器就會關閉抗悍。
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>}
但是生成器函數(shù)內(nèi)部處理了這個錯誤,那么生成器就不會關閉钳枕,還可以恢復執(zhí)行缴渊。錯誤處理會跳過對應的 yield
,因此在這個例子中會跳過一個值么伯。如下所示:
function* generator1() {
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}
在這里疟暖,向生成器內(nèi)部注入一個錯誤,該錯誤會被 yield
關鍵字拋出田柔,并且在生成器內(nèi)部的 try-catch
語句塊中處理了該錯誤俐巴。此時,生成器函數(shù)還是會繼續(xù)執(zhí)行硬爆,但下次調(diào)用 next()
方法就不會產(chǎn)生 example
值欣舵,而是產(chǎn)生 instance
值。
<small>注意:如果生成器對象還沒有開始執(zhí)行缀磕,那么調(diào)用 throw()
拋出的錯誤不會在函數(shù)內(nèi)部被捕獲缘圈,因為這相當于在函數(shù)塊外部拋出了錯誤。</small>
yield
ECMAScript 6 提供了 yield
關鍵字用來讓生成器函數(shù)暫停袜蚕,并且函數(shù)作用域的狀態(tài)會保留糟把。生成器對象會通過調(diào)用 next()
方法讓生成器函數(shù)恢復執(zhí)行。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
循環(huán)遣疯。
function* generator() {
for (const x of ["sample", "example", "instance"]) {
yield x;
}
}
for (const x of generator1()) {
console.log(x);
}
// sample
// example
// instance
<small>注意:yield
關鍵字只能在生成器函數(shù)內(nèi)部使用且必須位于生成器函數(shù)定義中,出現(xiàn)在嵌套的非生成器函數(shù)中會拋出錯誤凿傅。</small>
還可以使用 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;
}
<small>注意,yield
與 *
兩側(cè)的空格不影響其行為辨液。</small>
由于 yield*
可以調(diào)用另外一個生成器虐急,所以通過 yield*
可以實現(xiàn)遞歸調(diào)用。
function* recs(n) {
if (n > 1) {
yield* f(n-1);
}
yield n;
}
for (const x of recs(3)) {
console.log(x);
}
// 1
// 2
// 3
使用遞歸生成器結(jié)構(gòu)和 yield*
可以優(yōu)雅地表達遞歸算法滔迈。
小結(jié)
生成器是一種特殊的函數(shù)止吁,調(diào)用之后會返回一個生成器對象。生成器對象實現(xiàn)了 Iterable
接口亡鼠,因此應用場景與迭代器一樣赏殃。生成器支持 yield
關鍵字,用于暫停執(zhí)行生成器函數(shù)间涵,與 next()
方法搭配產(chǎn)生一系列值仁热。yield*
表達式可以在生成器中調(diào)用其它生成器。
更多內(nèi)容請關注公眾號「海人為記」