快速了解 ES6 的生成器

JavaScript 生成器.png

生成器是 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: trueIteratorObject 內(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)容請關注公眾號「海人為記

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末勾哩,一起剝皮案震驚了整個濱河市抗蠢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌思劳,老刑警劉巖迅矛,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異潜叛,居然都是意外死亡秽褒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門威兜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來销斟,“玉大人,你說我怎么就攤上這事椒舵÷煊唬” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵笔宿,是天一觀的道長犁钟。 經(jīng)常有香客問我,道長泼橘,這世上最難降的妖魔是什么涝动? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮炬灭,結(jié)果婚禮上捧存,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好昔穴,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著提前,像睡著了一般吗货。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狈网,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天宙搬,我揣著相機與錄音,去河邊找鬼拓哺。 笑死勇垛,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的士鸥。 我是一名探鬼主播闲孤,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼烤礁!你這毒婦竟也來了讼积?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤脚仔,失蹤者是張志新(化名)和其女友劉穎勤众,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲤脏,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡们颜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了猎醇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窥突。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖姑食,靈堂內(nèi)的尸體忽然破棺而出波岛,到底是詐尸還是另有隱情,我是刑警寧澤音半,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布则拷,位于F島的核電站,受9級特大地震影響曹鸠,放射性物質(zhì)發(fā)生泄漏煌茬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一彻桃、第九天 我趴在偏房一處隱蔽的房頂上張望坛善。 院中可真熱鬧,春花似錦、人聲如沸眠屎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽改衩。三九已至岖常,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間葫督,已是汗流浹背竭鞍。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留橄镜,地道東北人偎快。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像洽胶,于是被迫代替她去往敵國和親晒夹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容