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á)式的值hello,done屬性值false,表示遍歷還沒(méi)結(jié)束。
第二次調(diào)用,Generator函數(shù)從上一次yield表達(dá)式停下來(lái)的地方,一直執(zhí)行到下一個(gè)yield表達(dá)式。 next方法返回的對(duì)象的value屬性就是當(dāng)前yield表達(dá)式的值world,done屬性值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è)有著value和done兩個(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ě)法。
- 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);
}

當(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 遍歷了

代碼做點(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ù)行為。