摘要:最后,調(diào)用這個(gè)函數(shù),得到一個(gè)遍歷器對(duì)象并賦值給變量。值得注意的是如果函數(shù)內(nèi)部沒(méi)有部署代碼塊,那么遍歷器對(duì)象的方法拋出的錯(cuò)誤,將被外部代碼塊捕獲。
本文已同步至我的個(gè)人主頁(yè)。歡迎訪問(wèn)查看更多內(nèi)容!如有錯(cuò)誤或遺漏,歡迎隨時(shí)指正探討!謝謝大家的關(guān)注與支持!
一、什么是Generator函數(shù)Generator函數(shù)是ES6標(biāo)準(zhǔn)中提出的一種異步編程的解決方案。這種函數(shù)與普通函數(shù)最大的區(qū)別在于它可以暫停執(zhí)行,又可以從暫停的位置恢復(fù)繼續(xù)執(zhí)行。
從語(yǔ)法上看,Generator函數(shù)就是一個(gè)狀態(tài)機(jī),封裝了許多內(nèi)部狀態(tài)。
從實(shí)質(zhì)上看,Generator函數(shù)就是一個(gè)遍歷器對(duì)象生成器。(關(guān)于遍歷器對(duì)象,可以參考阮一峰老師的這篇文章)Generator函數(shù)返回一個(gè)遍歷器對(duì)象,遍歷這個(gè)對(duì)象,就可以依次得到函數(shù)內(nèi)部的每一個(gè)狀態(tài)。
二、基本語(yǔ)法 1、定義Generator函數(shù)定義一個(gè)Generator函數(shù)和定義一個(gè)普通函數(shù)的區(qū)別在于:
function關(guān)鍵字和函數(shù)名之間有一個(gè) *(星號(hào))。
函數(shù)內(nèi)部使用yield來(lái)定義每一個(gè)函數(shù)內(nèi)部的狀態(tài)。
如果函數(shù)內(nèi)部有return語(yǔ)句,那么他就是函數(shù)內(nèi)部的最后一個(gè)狀態(tài)。
來(lái)看一個(gè)簡(jiǎn)單的例子:
// 定義 function* sayHello() { yield "hello"; yield "world"; return "ending"; } // 調(diào)用 // 注意,hw獲取到的值是一個(gè)遍歷器對(duì)象 let g = sayHello();
上面的例子,定義了一個(gè)名為sayHello的Generator函數(shù),它內(nèi)部有兩個(gè)yield表達(dá)式和一個(gè)return表達(dá)式。所以,該函數(shù)內(nèi)部有三個(gè)狀態(tài):hello,world 和 return語(yǔ)句(結(jié)束執(zhí)行)。最后,調(diào)用這個(gè)函數(shù),得到一個(gè)遍歷器對(duì)象并賦值給變量g。
Generator函數(shù)的調(diào)用方法與普通函數(shù)完全一樣,函數(shù)名()。不同的是:
函數(shù)調(diào)用后,內(nèi)部代碼(從第一行開(kāi)始)都不會(huì)立即執(zhí)行。
函數(shù)調(diào)用后會(huì)有一個(gè)返回值,這個(gè)值是一個(gè)指向內(nèi)部狀態(tài)的指針對(duì)象,實(shí)質(zhì)就是一個(gè)包含函數(shù)內(nèi)部狀態(tài)的遍歷器對(duì)象。
Generator函數(shù)調(diào)用后不會(huì)立即執(zhí)行,那么,我們?nèi)绾巫屗_(kāi)始執(zhí)行內(nèi)部的代碼呢?又如何獲取它內(nèi)部的每一個(gè)狀態(tài)呢?此時(shí),我們必須調(diào)用返回的生成器對(duì)象的.next()方法,才能開(kāi)始代碼的執(zhí)行,并且使得指針移向下一個(gè)狀態(tài)。
以上面的例子為例:
g.next(); // { value: "hello", done: false } g.next(); // { value: "world", done: false } g.next(); // { value: "ending", done: true } g.next(); // { value: undefined, done: true }
上面的代碼中,一共調(diào)用了四次g這個(gè)遍歷器對(duì)象的.next()方法。第一次調(diào)用,sayHello這個(gè)Generator函數(shù)開(kāi)始執(zhí)行,直到遇到第一個(gè)yield表達(dá)式就會(huì)暫停執(zhí)行。.next()方法會(huì)返回一個(gè)對(duì)象,它的value屬性就是當(dāng)前yield表達(dá)式的值hello,done屬性的值false,表示遍歷還沒(méi)有結(jié)束。
第二次再調(diào)用.next(),就會(huì)執(zhí)行到第二個(gè)yield表達(dá)式處,并暫停執(zhí)行,返回對(duì)應(yīng)的對(duì)象。
第三次調(diào)用.next(),函數(shù)執(zhí)行到最后的return語(yǔ)句,此時(shí)標(biāo)志著遍歷器對(duì)象g遍歷結(jié)束,所以返回的對(duì)象中value屬性值就是return后面所跟的值ending,done屬性值為true,表示遍歷已經(jīng)結(jié)束。
第四次以及后面在調(diào)用.next()方法,返回的都會(huì)是{value: undefined, done: true }。
2、yield表達(dá)式由Generator函數(shù)返回的遍歷器對(duì)象,只有調(diào)用.next()方法才會(huì)遍歷到下一個(gè)內(nèi)部狀態(tài),所以這其實(shí)是提供了一種可以暫停執(zhí)行的函數(shù),yield表達(dá)式就是暫停標(biāo)志。
遍歷器對(duì)象的.next()方法的運(yùn)行邏輯如下。
遇到yield表達(dá)式,就暫停執(zhí)行后面的操作,并將緊跟在yield后面的那個(gè)表達(dá)式的值,作為返回的對(duì)象的value屬性值。
下一次調(diào)用.next()方法時(shí),再繼續(xù)往下執(zhí)行,直到遇到下一個(gè)yield表達(dá)式。
如果沒(méi)有再遇到新的yield表達(dá)式,就一直運(yùn)行到函數(shù)結(jié)束,直到return語(yǔ)句為止,并將return語(yǔ)句后面的表達(dá)式的值,作為返回的對(duì)象的value屬性值。
如果該函數(shù)沒(méi)有return語(yǔ)句,則返回的對(duì)象的value屬性值為undefined。
值得注意的是:
yield關(guān)鍵字只能出現(xiàn)在Generator函數(shù)中,出現(xiàn)在別的函數(shù)中會(huì)報(bào)錯(cuò)。
// 出現(xiàn)在普通函數(shù)中,報(bào)錯(cuò) (function () { yield "hello"; })() // forEach不是Generator函數(shù),報(bào)錯(cuò) [1, 2, 3, 4, 5].forEach(val => { yield val });
yield關(guān)鍵字后面跟的表達(dá)式,是惰性求值的。 只有當(dāng)調(diào)用.next()方法、內(nèi)部狀態(tài)暫停到當(dāng)前yield時(shí),才會(huì)計(jì)算其后面跟的表達(dá)式的值。這等于為JavaScript提供了手動(dòng)的“惰性求值”的語(yǔ)法功能。
function* step() { yield "step1"; // 下面的yield后面的表達(dá)式不會(huì)立即求值, // 只有暫停到這一行時(shí),才會(huì)計(jì)算表達(dá)式的值。 yield "step" + 2; yield "setp3"; return "end"; }
yield表達(dá)式本身是沒(méi)有返回值的,或者說(shuō)它的返回值為undefined。使用.next()傳參可以為其設(shè)置返回值。(后面會(huì)講到)
function* gen() { for (let i = 0; i < 5; i++) { let res = yield; // yield表達(dá)式本身沒(méi)有返回值 console.log(res); // undefined } } let g = gen(); g.next(); // {value: 0, done: false} g.next(); // {value: 1, done: false} g.next(); // {value: 2, done: false}
yield與return的異同:
相同點(diǎn):
兩者都能返回跟在其后面的表達(dá)式的值。
不同點(diǎn):
yield表達(dá)式只是暫停函數(shù)向后執(zhí)行,return是直接結(jié)束函數(shù)執(zhí)行。
yield表達(dá)式可以出現(xiàn)多次,后面還可以有代碼。return只能出現(xiàn)一次,后面的代碼不會(huì)執(zhí)行,在一些情況下還會(huì)報(bào)錯(cuò)。
正常函數(shù)只能返回一個(gè)值,因?yàn)橹荒軋?zhí)行一次return。Generator函數(shù)可以返回一系列的值,因?yàn)榭梢杂腥我舛鄠€(gè)yield。
3、.next()方法傳參前面我們說(shuō)到過(guò),yield表達(dá)式自身沒(méi)有返回值,或者說(shuō)返回值永遠(yuǎn)是undefined。但是,我們可以通過(guò)給.next()方法傳入一個(gè)參數(shù),來(lái)設(shè)置上一個(gè)(是上一個(gè))yield表達(dá)式返回值。
來(lái)看一個(gè)例子:
function* conoleNum() { console.log("Started"); console.log(`data: ${yield}`); console.log(`data: ${yield}`); return "Ending"; } let g = conoleNum(); g.next(); // 控制臺(tái)輸出:"Started" g.next("a"); // 控制臺(tái)輸出:"data: a" // 不傳入?yún)?shù)"a",就會(huì)輸出"data: undefined" g.next("b"); // 控制臺(tái)輸出:"data: b" // 不傳入?yún)?shù)"a",就會(huì)輸出"data: undefined"
上面的例子,需要強(qiáng)調(diào)一個(gè)不易理解的地方。
第一次調(diào)用.next(),此時(shí)函數(shù)暫停在代碼第三行的yield表達(dá)式處。記得嗎?yield會(huì)暫停函數(shù)執(zhí)行,此時(shí)打印它的console.log(),也就是代碼第三行的console,由于暫停并沒(méi)有被執(zhí)行,所以不會(huì)打印出結(jié)果,只輸出了代碼第二行的"Started"。
當(dāng)?shù)诙握{(diào)用.next()方法時(shí),傳入?yún)?shù)"a",函數(shù)暫停在代碼第四行的yield語(yǔ)句處。此時(shí)參數(shù)"a"會(huì)被當(dāng)做上一個(gè)yield表達(dá)式的返回值,也就是代碼第三行的yiled表達(dá)式的返回值,所以此時(shí)控制臺(tái)輸出"data: a"。而代碼第四行的console.log()由于暫停,沒(méi)有被輸出。
第三次調(diào)用,同理。所以輸出"data: b"。
4、Generator.prototype.throw()Generator函數(shù)返回的遍歷器對(duì)象,都有一個(gè).throw()方法,可以在函數(shù)體外拋出錯(cuò)誤,然后在Generator函數(shù)體內(nèi)捕獲。
function* gen() { try { yield; } catch (e) { console.log("內(nèi)部捕獲", e); } }; var g = gen(); // 下面執(zhí)行一次.next() // 是為了讓gen函數(shù)體執(zhí)行進(jìn)入try語(yǔ)句中的yield處 // 這樣拋出錯(cuò)誤,gen函數(shù)內(nèi)部的catch語(yǔ)句才能捕獲錯(cuò)誤 g.next(); try { g.throw("a"); g.throw("b"); } catch (e) { console.log("外部捕獲", e); }
上面例子中,遍歷器對(duì)象g在gen函數(shù)體外連續(xù)拋出兩個(gè)錯(cuò)誤。第一個(gè)錯(cuò)誤被gen函數(shù)體內(nèi)的catch語(yǔ)句捕獲。g第二次拋出錯(cuò)誤,由于gen函數(shù)內(nèi)部的catch語(yǔ)句已經(jīng)執(zhí)行過(guò)了,不會(huì)再捕捉到這個(gè)錯(cuò)誤了,所以這個(gè)錯(cuò)誤就會(huì)被拋出gen函數(shù)體,被函數(shù)體外的catch語(yǔ)句捕獲。
值得注意的是:
如果Generator函數(shù)內(nèi)部沒(méi)有部署try...catch代碼塊,那么遍歷器對(duì)象的throw方法拋出的錯(cuò)誤,將被外部try...catch代碼塊捕獲。
如果Generator函數(shù)內(nèi)部和外部都沒(méi)有部署try...catch代碼塊,那么程序?qū)?bào)錯(cuò),直接中斷執(zhí)行。
遍歷器對(duì)象的throw方法被捕獲以后,會(huì)附帶執(zhí)行一次.next()方法,代碼執(zhí)行會(huì)暫停到下一條yield表達(dá)式處。看下面這個(gè)例子:
function* gen(){ try { yield console.log("a"); } catch (e) { console.log(e); // "Error" } yield console.log("b"); yield console.log("c"); } var g = gen(); g.next(); // 控制臺(tái)輸出:"a" g.throw("Error"); // 控制臺(tái)輸出:"b" // throw的錯(cuò)誤被內(nèi)部catch語(yǔ)句捕獲, // 會(huì)自動(dòng)在執(zhí)行一次g.next() g.next(); // 控制臺(tái)輸出:"c"5、Generator.prototype.return()
Generator函數(shù)返回的遍歷器對(duì)象,還有一個(gè).return()方法,可以返回給定的值,并且直接結(jié)束對(duì)遍歷器對(duì)象的遍歷。
function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next(); // { value: 1, done: false } // 提前結(jié)束對(duì)g的遍歷。盡管yield還沒(méi)有執(zhí)行完 // 此時(shí)done屬性值為true,說(shuō)明遍歷結(jié)束 g.return("foo"); // { value: "foo", done: true } g.next(); // { value: undefined, done: true }
如果.return()方法調(diào)用時(shí),不提供參數(shù),則返回值的value屬性為undefined。
6、yield* 表達(dá)式yield* 用來(lái)在一個(gè)Generator函數(shù)里面執(zhí)行另一個(gè)Generator函數(shù)。
如果在一個(gè)Generator函數(shù)內(nèi)部,直接調(diào)用另一個(gè)Generator函數(shù),默認(rèn)情況下是沒(méi)有效果的。
function* gen1() { yield "a"; yield "b"; } function* gen2() { yield "x"; // 直接調(diào)用gen1() gen1(); yield "y"; } // 遍歷器對(duì)象可以使用for...of遍歷所有狀態(tài) for (let v of gen2()){ 只輸出了gen1的狀態(tài) console.log(v); // "x" "y" }
上面的例子中,gen1和gen2都是Generator函數(shù),在gen2里面直接調(diào)用gen1,是不會(huì)有效果的。
這個(gè)就需要用到 yield* 表達(dá)式。
function* gen1() { yield "a"; yield "b"; } function* gen2() { yield "x"; // 用 yield* 調(diào)用gen1() yield* gen1(); yield "y"; } for (let v of gen2()){ 輸出了gen1、gen2的狀態(tài) console.log(v); // "x" "a" "b" "y" }小節(jié)
本文主要講解Generator函數(shù)的基本語(yǔ)法和一些細(xì)節(jié),Generator函數(shù)的定義、yield表達(dá)式、.next()方法及傳參、.throw()方法、.return()方法以及 yield* 表達(dá)式。
文章開(kāi)頭講到,Generator函數(shù)時(shí)ES6提出的異步編程的一種解決方案。在實(shí)際應(yīng)用中,一般在yield關(guān)鍵字后面會(huì)跟隨一個(gè)異步操作,當(dāng)異步操作成功返回后調(diào)用.next()方法,將異步流程交給下一個(gè)yield表達(dá)式。具體關(guān)于Generator函數(shù)的異步應(yīng)用,大家可以參考阮一峰老師的這篇文章,或參考其他網(wǎng)上資料,繼續(xù)深入學(xué)習(xí)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/103133.html
摘要:調(diào)用函數(shù)后和普通函數(shù)不同的是,該函數(shù)并不立即執(zhí)行,也不返回函數(shù)執(zhí)行結(jié)果,而是返回一個(gè)指向內(nèi)部狀態(tài)的對(duì)象,也可以看作是一個(gè)遍歷器對(duì)象。第一個(gè)只是用來(lái)啟動(dòng)函數(shù)內(nèi)部的遍歷器,傳參也沒(méi)有多大意義。 之前斷斷續(xù)續(xù)接觸到了一些ES6的知識(shí),異步編程方面聽(tīng)得比較多的就是Promise,直到最近比較系統(tǒng)地學(xué)習(xí)了ES6的新特性才發(fā)現(xiàn)Generator這個(gè)神奇的存在,它可以實(shí)現(xiàn)一些前所未有的事情,讓我頓時(shí)...
摘要:同時(shí),迭代器有一個(gè)方法來(lái)向函數(shù)中暫停處拋出一個(gè)錯(cuò)誤,該錯(cuò)誤依然可以通過(guò)函數(shù)內(nèi)部的模塊進(jìn)行捕獲處理。 本文翻譯自:Diving Deeper With ES6 Generators 由于個(gè)人能力有限,翻譯中難免有紕漏和錯(cuò)誤,望不吝指正issue ES6 Generators:完整系列 The Basics Of ES6 Generators Diving Deeper With E...
摘要:關(guān)于協(xié)程和中的什么是協(xié)程進(jìn)程和線程眾所周知,進(jìn)程和線程都是一個(gè)時(shí)間段的描述,是工作時(shí)間段的描述,不過(guò)是顆粒大小不同,進(jìn)程是資源分配的最小單位,線程是調(diào)度的最小單位。子程序就是協(xié)程的一種特例。 關(guān)于協(xié)程和 ES6 中的 Generator 什么是協(xié)程? 進(jìn)程和線程 眾所周知,進(jìn)程和線程都是一個(gè)時(shí)間段的描述,是CPU工作時(shí)間段的描述,不過(guò)是顆粒大小不同,進(jìn)程是 CPU 資源分配的最小單位,...
摘要:示例運(yùn)行函數(shù)彈出彈出函數(shù)接收參數(shù),返回值。其中,返回一個(gè)對(duì)象,是的返回值,代表函數(shù)是否執(zhí)行完成。 ES6特性介紹(下) ES6新的標(biāo)準(zhǔn),新的語(yǔ)法特征:1、變量/賦值2、函數(shù)3、數(shù)組/json4、字符串5、面向?qū)ο?、Promise7、generator8、ES7:async/await 《【W(wǎng)eb全棧課程二】ES6特性介紹(上)》見(jiàn):https://segmentfault.com/a...
摘要:換句話說(shuō),我們很好的對(duì)代碼的功能關(guān)注點(diǎn)進(jìn)行了分離通過(guò)將使用消費(fèi)值得地方函數(shù)中的邏輯和通過(guò)異步流程來(lái)獲取值迭代器的方法進(jìn)行了有效的分離。但是現(xiàn)在我們通過(guò)來(lái)管理代碼的異步流程部分,我們解決了回調(diào)函數(shù)所帶來(lái)的反轉(zhuǎn)控制等問(wèn)題。 本文翻譯自 Going Async With ES6 Generators 由于個(gè)人能力知識(shí)有限,翻譯過(guò)程中難免有紕漏和錯(cuò)誤,還望指正Issue ES6 Gener...
摘要:前言本文就是簡(jiǎn)單介紹下語(yǔ)法編譯后的代碼。如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑?qǐng)務(wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎,對(duì)作者也是一種鼓勵(lì)。 前言 本文就是簡(jiǎn)單介紹下 Generator 語(yǔ)法編譯后的代碼。 Generator function* helloWorldGenerator() { yield hello; yield world; return ending...
閱讀 1849·2021-11-11 16:55
閱讀 1462·2019-08-30 15:54
閱讀 784·2019-08-29 15:34
閱讀 2263·2019-08-29 13:11
閱讀 2919·2019-08-26 13:28
閱讀 1886·2019-08-26 10:49
閱讀 1003·2019-08-26 10:40
閱讀 2564·2019-08-23 18:21