詳解generator(一)——generator基礎(chǔ)與iterator的關(guān)系

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)終止。

最后編輯于
?著作權(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)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 6,460評(píng)論 9 19
  • 特別說(shuō)明,為便于查閱,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 739評(píng)論 0 1
  • 在此處先列下本篇文章的主要內(nèi)容 簡(jiǎn)介 next方法的參數(shù) for...of循環(huán) Generator.prototy...
    醉生夢(mèng)死閱讀 1,488評(píng)論 3 8
  • 簡(jiǎn)介 基本概念 Generator函數(shù)是ES6提供的一種異步編程解決方案,語(yǔ)法行為與傳統(tǒng)函數(shù)完全不同。本章詳細(xì)介紹...
    呼呼哥閱讀 1,136評(píng)論 0 4
  • 你不知道JS:異步 第四章:生成器(Generators) 在第二章,我們明確了采用回調(diào)表示異步流的兩個(gè)關(guān)鍵缺點(diǎn):...
    purple_force閱讀 1,054評(píng)論 0 2

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