Generator函數(shù)的語(yǔ)法

1. 簡(jiǎn)介

Generator函數(shù)是es6提供的一種異步編程的解決方案,語(yǔ)法行為與傳統(tǒng)函數(shù)完全不一樣。

Generator函數(shù)有多種理解角度,從語(yǔ)法上,首先可以把它理解成,Generator函數(shù)是一個(gè)狀態(tài)機(jī),封裝了多個(gè)內(nèi)部狀態(tài)。

執(zhí)行Generator函數(shù)會(huì)返回一個(gè)遍歷器對(duì)象,也就是說(shuō),Generator函數(shù)除了是狀態(tài)機(jī)還是一個(gè)遍歷器對(duì)象生成函數(shù)。 返回遍歷器對(duì)象,可以依次遍歷Generator函數(shù)內(nèi)部的每一個(gè)狀態(tài)。

形式上Generator函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征。 一是,function關(guān)鍵字與函數(shù)之間有一個(gè) * ,二是,函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài)。

function* gen(){
    yield "hello";
    yield "world";
    return "ends"
}

let g1=gen()

上面代碼定義了一個(gè)Generator函數(shù)gen,它內(nèi)部有兩個(gè)yield表達(dá)式("hello"和"world"),即該函數(shù)有三個(gè)狀態(tài),hello,world和return語(yǔ)句。

Generator函數(shù)的調(diào)用和普通函數(shù)的調(diào)用一樣,也是在后面加上圓括號(hào)。

不同的是,調(diào)用Generator函數(shù)后,函數(shù)并不執(zhí)行,返回的也不是函數(shù)運(yùn)行結(jié)果,而是指向內(nèi)部應(yīng)用狀態(tài)的指針對(duì)象,就是遍歷器對(duì)象。

下一步,必須調(diào)用遍歷器對(duì)象的next方法,使得指針移向下一個(gè)狀態(tài)。也就是說(shuō),每次調(diào)用next方法,內(nèi)部指針就從函數(shù)頭部或者上次停下的地方開(kāi)始執(zhí)行,直到遇到下一個(gè)yield表達(dá)式(或return語(yǔ)句為止)。

換言之,Generator函數(shù)是分段執(zhí)行的。yield表達(dá)式只是暫停的標(biāo)志,而next方法可以恢復(fù)執(zhí)行。


function* gen(){
    yield "hello";
    yield "world";
    return "ends"
}


let g1=gen()

console.log(g1.next());    //  {value:'hello',done:false}

console.log(g1.next())     //  {value:"world",done:false}

console.log(g1.next())     //  {value:"ends",done:true}

console.log(g1.next())     // {value:undefined,done:true}

console.log(g1.next())     // {value:undefined,done:true}

第一次調(diào)用,Generator函數(shù)開(kāi)始執(zhí)行,直到遇到第一個(gè)yield表達(dá)式為止。 next方法返回一個(gè)對(duì)象,它的value屬性就是當(dāng)前yield表達(dá)式的值hellodone屬性值false,表示遍歷還沒(méi)結(jié)束。

第二次調(diào)用,Generator函數(shù)從上一次yield表達(dá)式停下來(lái)的地方,一直執(zhí)行到下一個(gè)yield表達(dá)式。 next方法返回的對(duì)象的value屬性就是當(dāng)前yield表達(dá)式的值worlddone屬性值false表示當(dāng)前遍歷還沒(méi)結(jié)束。

第三次調(diào)用,Generator函數(shù)從上一次yield表達(dá)式停下來(lái)的地方,一直執(zhí)行到return語(yǔ)句(如果沒(méi)有return語(yǔ)句,就執(zhí)行到函數(shù)結(jié)束)。next返回的對(duì)象的value屬性,就是緊跟在return語(yǔ)句后面的表達(dá)式的值。如果沒(méi)有return語(yǔ)句,則屬性的值為undefined

以后再調(diào)用這個(gè)方法,返回的都是這個(gè)值。

總結(jié)一下,調(diào)用Generator函數(shù),返回的都是一個(gè)遍歷器對(duì)象,代表Generator函數(shù)內(nèi)部指針。 以后每次調(diào)用Generator函數(shù)的next方法,就會(huì)返回一個(gè)有著valuedone兩個(gè)屬性的對(duì)象。 value屬性表示當(dāng)前內(nèi)部狀態(tài)的值,是yield表達(dá)式后面那個(gè)表達(dá)式的值;done屬性值是一個(gè)布爾值,表示當(dāng)前遍歷是否結(jié)束。

es6沒(méi)有規(guī)定,function關(guān)鍵字與函數(shù)名之間的星號(hào),寫(xiě)在哪個(gè)位置,這導(dǎo)致下面的寫(xiě)法都能通過(guò)。

function * g1(){}

function *g1(){}

function* g1(){}

function*g1(){}

我們一般采用第三種寫(xiě)法。


  1. yield表達(dá)式



??由于Generator函數(shù)返回的是遍歷器對(duì)象,只有調(diào)用next方法才會(huì)遍歷下一個(gè)內(nèi)部狀態(tài),所以實(shí)際提供了一個(gè)可以暫停執(zhí)行的函數(shù)。

yield表達(dá)式就是暫停的標(biāo)志。

遍歷器對(duì)象的next方法運(yùn)行邏輯如下。

(1) 遇到y(tǒng)ield表達(dá)式,就暫停執(zhí)行后面的操作,并將緊跟在yield后面的那個(gè)表達(dá)式的值,作為返回對(duì)象的value值。

(2)下一次再調(diào)用next方法時(shí),再繼續(xù)往下執(zhí)行,直到再遇到下一個(gè)yield表達(dá)式。

(3)如果沒(méi)有再遇到新的yield表達(dá)式,就一直運(yùn)行到函數(shù)結(jié)束,直到return語(yǔ)句為止,并將return表達(dá)式后面的值,作為返回對(duì)象的value屬性值。

(4)如果該對(duì)象沒(méi)有return語(yǔ)句,則返回對(duì)象的value屬性值為undefined

需要注意的是,yield表達(dá)式后面的表達(dá)式,只有當(dāng)調(diào)用next方法、內(nèi)部指針指向該語(yǔ)句時(shí),才會(huì)執(zhí)行。 因此等于說(shuō)是為javascript提供了惰性求值的語(yǔ)法功能。

function* gen(){
    yield 123+456
}

let g1=gen()

console.log(g1.next())               //    {value:579,done:false} 

上面代碼中,yield后面的表達(dá)式123+456,不會(huì)立即求職,只會(huì)在next方法指針移到這一句話(huà)時(shí),才會(huì)求職。

yield表達(dá)式與return語(yǔ)句既有相似之處,又有區(qū)別。 相似之處在于,都能返回緊跟在語(yǔ)句后面那個(gè)表達(dá)式的值。 不同之處在于,每次遇到yield,函數(shù)暫停執(zhí)行,下次再?gòu)脑撐恢美^續(xù)往后執(zhí)行。 而return語(yǔ)句不具備位置記憶功能。 一個(gè)函數(shù)里面,只能執(zhí)行一次(或者說(shuō)只能有1個(gè)return語(yǔ)句),但是可以執(zhí)行多次yield表達(dá)式。 正常函數(shù)只能返回一個(gè)值,因?yàn)橹荒軋?zhí)行一次return, Generator函數(shù)可以返回一系列的值,因?yàn)榭梢杂腥我舛鄠€(gè)yield。

從另一個(gè)角度看,也可以說(shuō)是Generator生成一些列的值。這就是它名稱(chēng)的由來(lái),yield在英文意思中表示"產(chǎn)出"。

Generator函數(shù)可以不用yield表達(dá)式,這時(shí)就變成了一個(gè)單純的暫緩執(zhí)行函數(shù)。

function* gen(){
    console.log("執(zhí)行了!")
}

let g1=gen()

console.log(g1);   //  gen {<suspended>}

上面代碼中,如果函數(shù)gen是一個(gè)普通函數(shù)的話(huà),那么調(diào)用的時(shí)候就會(huì)立即執(zhí)行,打印"執(zhí)行了!",但是作為一個(gè)Generator函數(shù),調(diào)用的時(shí)候他是不會(huì)執(zhí)行的,只有在你調(diào)用了next方法后才會(huì)執(zhí)行。


function* gen(){
    console.log("執(zhí)行了!");  //  執(zhí)行了!
}

let g1=gen()

console.log(g1.next());   //  {value:undefined,done:true}

只有在調(diào)用了next方法后才會(huì)執(zhí)行。

另外需要注意,yield表達(dá)式只能用在Generator函數(shù)里面,用在普通的函數(shù)里面會(huì)報(bào)錯(cuò)。

function f(){
    yield 123
}
   //  Uncaught SyntaxError: Unexpected number


另外,yield表達(dá)式如果在另一個(gè)表達(dá)式中,必須放上圓括號(hào)。

function* demo() {
  console.log('Hello' + yield); // SyntaxError
  console.log('Hello' + yield 123); // SyntaxError

  console.log('Hello' + (yield)); // OK
  console.log('Hello' + (yield 123)); // OK
}


yield表達(dá)式用作函數(shù)參數(shù),或者放在表達(dá)式的右邊,可以不加括號(hào)。

function* demo() {
  foo(yield 'a', yield 'b'); // OK
  let input = yield; // OK
}



3.與iterator接口的關(guān)系

任意一個(gè)對(duì)象的 Symbol.iterator方法,等于該對(duì)象的遍歷器生成函數(shù),調(diào)用該函數(shù)會(huì)返回該對(duì)象的一個(gè)遍歷器對(duì)象。

由于Generator函數(shù)就是遍歷器生成函數(shù),因此可以把Generator賦值給對(duì)象的Symbol.iterator屬性,從而使得該對(duì)象具有Iterator接口。

// 正常情況下,object是不能被for...of被遍歷的

var obj={
        name:"Andy",
        age:30,
        salary:1000
}


for(let i of obj){
    console.log(i);
}
obj

當(dāng)嘗試使用for...of 去遍歷object時(shí),控制會(huì)報(bào)錯(cuò), obj is not iterable 對(duì)象不能被遍歷

下面我們嘗試為對(duì)象部署iterator接口

var obj={}

obj[Symbol.iterator]=function* (){
    yield 1;
    yield 2;
    yield 3;
}


for(let i of obj){
    console.log(i);
}

現(xiàn)在這個(gè)obj對(duì)象可以被for...of 遍歷了

iterator



代碼做點(diǎn)小修改,使得obj 可以被for...of遍歷,控制臺(tái)打印obj的value

var obj={name:"Andy",age:30,weight:180}

obj[Symbol.iterator]=function* (){

    //  Object.keys()方法返回的是一個(gè)數(shù)組,數(shù)組中的元素是對(duì)象所有的key
    var key=Object.keys(obj);  //  ["name","age","weight"]

    for(let i=0;i<key.length;i++){
        yield key[i];
    }
    
}




for(let i of obj){
    console.log(obj[i])
}

//  chrome控制臺(tái)打印

//  Andy
//  30
//  180



2. next方法的參數(shù)

yield表達(dá)式本身沒(méi)有返回值,或者說(shuō)總是返回undefined。 next方法可以帶一個(gè)參數(shù),該參數(shù)就會(huì)被當(dāng)做上一個(gè)yield表達(dá)式的返回值。


function* gen(){
    for(let i=0;i<5;i++){
        let reset=yield i;
        console.log(reset)
    }

}

var g=gen();

g.next()  

g.next()   //  undefined      gen.js:5 提示第5行打印

上面代碼中,Generator函數(shù)gen,將表達(dá)式 yield i賦值給reset,控制臺(tái)打印reset,顯示的是undefined。

如果next方法沒(méi)有帶參數(shù),每次運(yùn)行到y(tǒng)ield表達(dá)式變量reset的值總是undefined。

下面我們嘗試將next方法帶參數(shù),再來(lái)看看控制臺(tái)打印結(jié)果。


function* gen(){
    for(let i=0;i<5;i++){
        var reset=yield i;
        console.log(reset)
    }

}

var g=gen();

g.next(123)  

g.next(456)   //  456      gen.js:5 提示第5行打印

上面代碼中,第一次調(diào)用g.next()方法后,代碼運(yùn)行到 var reset=yield i 這條語(yǔ)句,因此沒(méi)有打印值。

下次再調(diào)用 g.next() 方法后,由于next方法攜帶了參數(shù)456,因此456就作為了yield表達(dá)式的返回值。

這個(gè)功能有很重要的語(yǔ)法意義。 Generator函數(shù)從暫停狀態(tài)到恢復(fù)運(yùn)行,它的上下文狀態(tài)(context)是不變的。 通過(guò)next方法的參數(shù),就有辦法再Generator函數(shù)開(kāi)始運(yùn)行之后,繼續(xù)向函數(shù)體內(nèi)部注入值。 也就是說(shuō),可以在Generator函數(shù)運(yùn)行的不同階段,從外部向內(nèi)部注入值,從而調(diào)整函數(shù)行為。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容