generator基礎(chǔ)
generator的結(jié)構(gòu)和函數(shù)的構(gòu)成相同,只不過(guò)聲明格式不同,如:function *foo(){}或function*foo(){}(有無(wú)空格)。
我們先看一個(gè)generator簡(jiǎn)單的例子:
let a=1
function *foo(){
a++
yield
console.log(`a:${a}`)
}
function bar(){
a++
}
// 構(gòu)造迭代期it控制generator
const it=foo()
// 啟動(dòng)foo()
it.next() // a->2
bar() // a->3
it.next() // 打印: a:3
記住!第一個(gè)next()是初始化啟動(dòng)generator
輸入輸出
1.迭代消息傳遞
function *foo(x){
const y=x*(yield)
return y
}
const it=foo(2)
// 初始化啟動(dòng)*foo(),此時(shí)暫停在賦值語(yǔ)句中間
it.next() // ->{value: undefined, done: false}
// 把3傳回被暫停的yield表達(dá)式,此時(shí)賦值語(yǔ)句為const y=2*3
const res=it.next(3) // ->{value: 6, done: true}
res.value // 6
因?yàn)榈谝粋€(gè)next()總要啟動(dòng)generator,所以next()和yield并不是一一匹配的嗎?
2.雙向消息傳遞
我們從迭代器角度來(lái)看next()和yield的關(guān)系:yield ...作為一個(gè)表達(dá)式可以發(fā)出消息響應(yīng)next(..)調(diào)用,next(..)也可以向暫停的yield表達(dá)式發(fā)送值————消息是雙向傳遞的。
改一下上面的例子:
function *foo(x){
const y=x*(yield 'hello')
return y
}
const it=foo(2)
it.next() //-> {value: "hello", done: false}
it.next(3) // ->{value: 6, done: true}
在generator起始處,我們調(diào)用第一個(gè)next()時(shí),還沒(méi)有暫停的yield來(lái)接受這樣一個(gè)值。第一個(gè)next()的調(diào)用,基本上在提出一個(gè)問(wèn)題:generator *foo要給我的下一個(gè)值是什么,由第一個(gè)yield 'hello'來(lái)回答這個(gè)問(wèn)題。
多個(gè)迭代器
在普通的函數(shù)中,想要兩個(gè)函數(shù)交替執(zhí)行是不可能的!要么A函數(shù)先運(yùn)行完畢,要么B函數(shù)先運(yùn)行完畢。
然而,使用generator交替執(zhí)行顯然是又肯能的!
let a=1
let b=2
function *foo(){
a++
yield
b=b*a
a=(yield b)+3
}
function *bar(){
b--
yield
a=(yield 8)+b
b=a*(yield 2)
}
上面兩個(gè)generator在共享的相同變量上迭代交替執(zhí)行,根據(jù)迭代器的控制,前面的程序可以產(chǎn)生多種不同的結(jié)果。
我們創(chuàng)建一個(gè)輔助函數(shù)來(lái)控制迭代器:
function step(gen){
let it=gen()
let last;
return function(){
// 不管yield的是啥,下一次都把他傳回去
last=it.next(last).value
}
}
我們來(lái)試驗(yàn)交替運(yùn)行*foo()和*bar()。
const s1=step(foo)
const s2=step(bar)
s2() // b-- b=1
s2() // yield 8
s1() // a++ a=2
s2() // a=8+b a=9
// yield 2
s1() // b=b*a b=9
// yield b
s1() // a=b+3 12
s2() // b=a*2 18 這里的a在第四步已經(jīng)被賦值為9了
console.log(a,b) // 12 18
generator的產(chǎn)生值
在上一節(jié)中主要介紹了generator產(chǎn)生值的方式,這一節(jié)將介紹一些更基礎(chǔ)的東西。
iterator
假設(shè)你要生成一系列的值,每個(gè)值與前一個(gè)都有特定的關(guān)系,這就是一個(gè)迭代過(guò)程。iterator是一個(gè)定義良好的接口,用于從generator中一步步地得到一系列的值,每次想要從generator中得到下一個(gè)值的時(shí)候需要調(diào)用next()。
用函數(shù)閉包構(gòu)成迭代器
我們可以用簡(jiǎn)單的函數(shù)閉包構(gòu)造生成一組數(shù)字的迭代過(guò)程。
const gimmeSomething = (() => {
let nextVal;
return () => {
if (nextVal === undefined) {
nextVal = 1
} else {
nextVal = (3 * nextVal) + 6
}
return nextVal
}
})()
gimmeSomething() //1
gimmeSomething() //9
gimmeSomething() // 33
...
我們?cè)贅?gòu)造一個(gè)next()接口:
const something=(()=>{
let nextVal
return {
[Symbol.iterator]:function(){return this}, //
next:()=>{
if(nextVal===undefined){
nextVal=1
}else{
nextVal=nextVal*3+6
}
return {done:false,value:nextVal}
}
}
})()
自動(dòng)迭代
es6中新增for...of循環(huán),可以自動(dòng)迭標(biāo)準(zhǔn)迭代器:
for (var v of something) {
console.log(v)
if (v > 300) break;
}
// 1 9 33 105 321
我們更改下上個(gè)例子:
const something = (() => {
let nextVal
return {
[Symbol.iterator]: function() {
return this
},
next: () => {
if (nextVal === undefined) {
nextVal = 1
} else {
nextVal = nextVal * 3 + 6
}
return {
done: nextVal>300,
value: nextVal
}
}
}
})()
for (var v of something) {
console.log(v) // 1 9 33 105
}
for..of..循環(huán)在每次迭代中自動(dòng)調(diào)用next(),它不會(huì)向next()傳入任何值,并且會(huì)在接受到done:true時(shí)停止。
除了構(gòu)造自定義的iterator,數(shù)組也有默認(rèn)的迭代器:
const a=[1,5,15,25]
for (var v of a) {
console.log(v) // 1,5,15,25
}
一般的object是沒(méi)有像array一樣的默認(rèn)迭代器,如果想要迭代一個(gè)對(duì)象的所有屬性的話,通過(guò)Object.keys(..)返回一個(gè)array,之后用for..of..迭代這個(gè)鍵名數(shù)組(注意:Object.keys并不包含來(lái)自于原型鏈上的屬性,而for..in..則包含)。
2.iterable
iterable指的是一個(gè)包含可以在其值上迭代的迭代器的對(duì)象,他的名稱是符號(hào)值Symbol.iterator,調(diào)用這個(gè)函數(shù)時(shí),他會(huì)返回一個(gè)迭代器。for..of循環(huán)自動(dòng)調(diào)用Symbol.iterator函數(shù)來(lái)構(gòu)建一個(gè)迭代器。
對(duì)于上一節(jié)使用for..of迭代數(shù)組的例子,就相當(dāng)于
const a=[1,5,15,25]
const it=a[Symbol.iterator]()
it.next().value //1
it.next().value //5
it.next().value // 15
3.生成器迭代器
我們可以把genrator看作一個(gè)值的生產(chǎn)者,我們通過(guò)迭代器接口的next()調(diào)用一次提取出一個(gè)值。
當(dāng)你執(zhí)行一個(gè)generator時(shí)就得到了一個(gè)iterator。我們用generator實(shí)現(xiàn)上一節(jié)無(wú)限數(shù)字序列生產(chǎn)者something。
function *something(){
let nextVal
while(true){
if(nextVal===undefined){
nextVal=1
}else{
nextVal=nextVal*3+6
}
yield nextVal
}
}
通常我們?cè)诰帉慾s程序時(shí),使用
while(true)且其中沒(méi)有退出語(yǔ)句時(shí)是一個(gè)非常糟糕的設(shè)計(jì),而如果在生成器中有yield的話,使用這樣的循環(huán)完全沒(méi)有問(wèn)題!因?yàn)樯善鲿?huì)在每次迭代中暫停,通過(guò)yield返回到主程序或時(shí)間循環(huán)隊(duì)列中。
使用for..of循環(huán)這個(gè)生成器:
for(let v of something()){
console.log(v)
if(v>300) break; // 1 9 33 105 321
}
我們首先要將generator -> iterator才能使用for .. of迭代,所以先something()構(gòu)造一個(gè)生產(chǎn)者來(lái)讓for..of..循環(huán)迭代。generator的迭代器iterable也有一個(gè)Symbol.iterator函數(shù),所以也是一個(gè)iterable。
停止生成器
在上面的例子中,generator *something()的迭代器實(shí)例在循環(huán)中break后就,似乎永遠(yuǎn)停留在掛起狀態(tài)。
事實(shí)上,在for..of循環(huán)的“異常結(jié)束”(提前終止),通常由break、return或未捕獲異常引起,會(huì)向generator的迭代器發(fā)送一個(gè)信號(hào)使其終止。
使用try..finally..語(yǔ)句構(gòu)建generator,即使generator已經(jīng)在外部結(jié)束了,但finally中的語(yǔ)句會(huì)被執(zhí)行,可以在其中清理資源,如:
function *something(){
try{
let nextVal
while(true){
if(nextVal===undefined){
nextVal=1
}else{
nextVal=nextVal*3+6
}
yield nextVal
}
}
finally{
console.log('clean up')
}
}
for(let v of something()){
console.log(v)
if(v>300) break; // 1 9 33 105 321 'clean up'
}
上面的例子中,break會(huì)觸發(fā)finally語(yǔ)句;我們也可以用it.return()手動(dòng)終止生成器的迭代器實(shí)例:
const it=something()
for(let v of it){
console.log(v)
if(v>300){
console.log(
it.return('end up!!').value
)
// 無(wú)需break
}
}
// 1 9 33 105 321
// clean up!
// end up!!
調(diào)用it.return()后,他會(huì)立即終止生成器,觸發(fā)finally中的語(yǔ)句,并且將返回的value設(shè)置為return()中傳入的內(nèi)容,done為true,也就是end up!被傳出的過(guò)程,for..of循環(huán)終止。