JavaScript - Generator

Generator 英文翻譯是 生產(chǎn)者,產(chǎn)生者.

定義方式如下.

function *gen() {
    //xxxxxx
    yield xxxx
    //xxxxx
    let a = yield xxxx.
    
    return xxx

}
  • 使用 function * 來定義
  • 在此方法內部可以使用 yield & return

Generator 函數(shù)和普通函數(shù)的區(qū)別

一個普通函數(shù),如果一旦執(zhí)行了,就會從頭走到尾,不會中止.

function normalFn() {
    console.log('start')
    console.log('middle')
    console.log('end')
}

normalFn()
普通函數(shù)從頭走到尾断医,不會中斷

同樣的代碼放在generator函數(shù)里面

function *gen() {
    console.log('start')
    yield
    console.log('middle')
    yield
    console.log('end')
}

// 返回一個枚舉器
let g = gen()
g.next() // 輸出 'start'
g.next() // 輸出 'middle'
g.next() // 輸出 'end'

發(fā)現(xiàn)代碼必須手動的調用gen()返回的枚舉器的next()方法,代碼才會執(zhí)行.

其中有 yield 這個關鍵字,yield的英文意思是:放棄

用一張圖去理解的話:

感性的理解yield

也就是說,每次調用 next() 方法,gen 函數(shù)里的代碼就會往下走一步,每次走到一個 yield 就停止執(zhí)行.

generator內部通過yield關鍵字,將代碼中斷,除非你調用next(),否則,generator函數(shù)不會繼續(xù)往下執(zhí)行.


關于 yield 關鍵字

generator方法,返回一個枚舉器.
每次調用 next() 在碰到 yield 的時候,會停止運行,執(zhí)行 yield 后面的表達式,并返回.

function *__gen() {
    yield 'hello'
    yield 'world'
}

let g = __gen()

console.log(g.next()) // {value:'hello',done:false}
console.log(g.next()) // {value:'world',done:false}
console.log(g.next()) // {value:undefined,done:true}

next()方法返回了一個對象静秆,包含value&done兩個屬性

那第一個例子,單純的使用 yield ,后面沒有接任何表達式,返回的對象的valueundefined嗎?


function *gen() {
  console.log('start')
  yield
  console.log('middle')
  yield
  console.log('end')
}

// // 返回一個枚舉器
let g = gen()
// g.next() // 輸出 'start'
// g.next() // 輸出 'middle'
// g.next() // 輸出 'end'

console.log(g.next()) //{value:undefined,done:false}
console.log(g.next()) //{value:undefined,done:false}
console.log(g.next()) //{value:undefined,done:true}
yield后面沒跟表達式 value就=undefined

也就是說,next() 會返回一個對象,對象的.value 屬性就是 yield 后面表達式返回的值.

yield可以返回值:next()==>{value:yield表達式,done:boolean}

yield 關鍵字,將 generator中斷,并返回yield后面表達式的值賦值給next(){value屬性}(如果有),
并等待下一次的 next()


關于 generator 里寫 return 關鍵字

我們發(fā)現(xiàn)在 generator 中,如果有 nyield,我們就需要手動的調用 n+1next() 才能得到 done:true 的結果.

但是最后一次的next() 返回的對象中 .value=undefined..

我們也可以使用在generator 中使用 return 關鍵字的方式來告知generator迭代結束,并將 return 返回的給過,給最后一個 next() 返回對象的value 屬性.

function *__gen() {
    yield 'hello'
    yield 'world'
    yield '!!!'
}

let g = __gen()
console.log(g.next()) // {value:'hello',done:false}
console.log(g.next()) // {value:'world',done:false}
console.log(g.next()) // {value:'!!!',done:true}

[圖片上傳失敗...(image-2417dc-1544290199232)]

如果不想讓最后一次 next() 的 value 是 undefined ,而是想具體的返回一個值,那么就在 generator 里使用 return 關鍵字.

兩個作用

  • 告知 generator 迭代結束
  • 把return的值,給最后一個迭代返回對象的.value屬性.

模擬一下 generator 返回對象的next()方法.

我是使用 next() 方法,來每次執(zhí)行一段一直到 yield 的代碼段.

next() 方法每次回返回一個對象,包括 valuedone 兩個屬性.

其中,valueyield 后面表達式的值. done 表示迭代是否結束.

generator 很高級,在代碼級別實現(xiàn)了中斷.

但我們也可以使用數(shù)組,模擬一個 next() 執(zhí)行過程,來加深對迭代的理解.

準備在數(shù)組上模擬一個generator中的next()方法.

核心就兩個

  • 每一次返回對應的元素 value
  • 迭代是否完成 done
Array.prototype.next = function () {
  this.index = this.index || 0
  return {
    value: (this.index < this.length ? this[this.index] : undefined),
    done: this.index++ >= this.length ? true : false
  }
}

  ;
let arr = [1, 2, 3]
console.log (arr.next())
console.log (arr.next())
console.log (arr.next())
console.log (arr.next())
用數(shù)組模擬generator的next()方法

關于 next() 方法的參數(shù)問題.

我們現(xiàn)在知道了,每次調用 next()方法

  • 都會執(zhí)行到下一個 yield 停止

  • 返回停止yield后面的表達式結果.

function *__gen() {
    console.log('第一階段')
    yield 'hello'
    console.log('第二階段')
    yield 'world'
    
    return '!!!' // 告知 generator 迭代結束,并把 !!! 傳遞給最后一次next() 返回對象的 .value 屬性
}


let g = __gen()

var res1 = g.next()
res1.value === 'hello' // true
var res2 = g.next()
res2.value === 'world' // true
var res3 = g.next()
res3.value === '!!!'  // true & {done:true}

那么既然 next() 是一個方法,方法是可以傳遞參數(shù)的.
就像在定義 generator 方法傳遞參數(shù)一樣.

function *__gen(p1,p2) {
    console.log(p1,p2)
}

let g = __gen(1,2)
g.next() // 輸出 1,2 {value:undefined,done:true}

next() 方法的參數(shù)是如何傳遞的呢?

可以先看一段代碼.

function *__gen() {
    let a = yield 'hello'
    console.log(`a:${a}`)
    let b = yield 'world'
    console.log(`b:$炼蹦`)
    return '!!!'
}

let g = __gen()
g.next()
g.next()
g.next()

先感性上猜測一下:

let a = yield 'hello'

ahello 嗎?

我們之前說過,yield 后面返回的值,給next()返回的對象的.value了.
所以,這里的 a 是不是 hello 不好說.

同理 b 是不是 world 也不好說.

按照我們以前的理解, let xx = xxx 都是把表達式右邊的結果返回給左邊.

在這里也會是這樣嗎?

undefined & undefined

突然發(fā)現(xiàn),之前的想法全是錯的.出來的是undefined,undefined

hello,world 哪去了?

記得之前說的嗎?

yield 的返回值,給對象了.

console.log(g.next())
console.log(g.next())
console.log(g.next())
yield返回的數(shù)據(jù)在next()方法返回對象的.value身上

所以,let a = yield 'hello' 這里的 a 和后面的 hello 沒有一毛錢關系.

let a 和 yield 'hello' 沒有一毛錢關系

最終結果輸出的是兩個 undefined .
那generator里寫的 let a,let b 有啥意義啊?
yield 后面的值給的又不是它們.


next 也是一個函數(shù),既然是函數(shù),我們就可以給它傳遞參數(shù).

js 里的人一個函數(shù),我們可以傳遞參數(shù),而不管之前的函數(shù)是如何定義的.

function noExplicitParam() {
    console.log(arguments[0])
    console.log(arguments[1])
}

noExplicitParam(1,2)

js的函數(shù)參數(shù)很靈活

所以,先不管 generator 的返回對象的 next() 方法是如何定義的.
我們就傳參數(shù)試試.

function *__gen() {
  let a = yield 'hello'
  console.log(`a:${a}`)
  let b = yield 'world'
  console.log(`b:$息拜`)
  return '!!!'
}


let g = __gen()
g.next(1)
g.next(2)
g.next(3)

還是先猜一下.

  • 當執(zhí)行第一個 g.next(1) 碰到了 yield 'hello' , 代碼終止,hello給了對象的.value , 1 給了 變量 a,
  • 當執(zhí)行第二個g.next(2),碰到了 yield 'world',代碼終止,world給了對象的.value,2給了b
  • 3由于后面沒有let c 去接收,所以不會輸出.

所以,輸出應該是 a:1 b:2

查看結果:

輸出結果 a:2 b:3

居然輸出的是a:2 b:3,我滴媽啊.那第一個g.next(1)傳遞的1哪去了?????

總是有驚喜.....

然而實際情況是:

  • 每次調用next()方法,都會執(zhí)行到一個yield結束.
    • 返回 yield后面的表達式給對象的.value(如果有)
    • 代碼暫停,等待下一次的 next()

上面只說了表達式右邊的yield情況,對左邊的let x 沒有絲毫提起.

其實這也是為什么 1 消失了的原因.

使用大神們的博客說明:

上面介紹的next方法均沒有參數(shù)艳悔,其實可以為它提供一個參數(shù)集嵌。參數(shù)會被當作上一個yield語句的返回值。如果沒有參數(shù)放吩,那默認上一個yield語句的返回值為undefined智听。

反正我是看起來比較費勁.

一張我自己理解的圖來說明為什么 1 消失了.

yield的有效范圍

回憶以下之前說的,每一次執(zhí)行到next()程序都會中止,等待下一個next()

  • 當執(zhí)行到紅色矩形的g.next(1) 時, 執(zhí)行的是上述紅色范圍的區(qū)域.

    • 由于 let a = 不屬于第一個 yield 的區(qū)域,所以 g.next(1) 參數(shù) 1 壓根就沒有變量去接收.
  • 當執(zhí)行到藍色矩形的g.next(2)時,執(zhí)行的是上述藍色范圍的代碼段.

    • 由于 let a = 屬于藍色代碼的區(qū)域,屬于第二個 yield 的有效范圍,所以 2 就被賦值到 a .輸出結果是 a:2
  • 當執(zhí)行到紫色矩形 g.next(3)時,執(zhí)行的是上述紫色代碼的區(qū)域.

    • 由于let b = 屬于紫色代碼的有效區(qū)域,屬于第三個yield的有效范圍,所以 3 賦值給了 b,最終輸出結果是 b:3

所以,最終輸出結果是 a:2,b:3 而不是 a:1,b:2.

每一次next()的執(zhí)行,真真正正的只到y(tǒng)ield而已.(知道yield的右邊,yield的左邊數(shù)與下一個yield的有效范圍)

只要正確的識別了 yield 的有效區(qū)域,就能很快的知道 next() 是如何傳遞參數(shù)的.

如果寫這么一代碼,就很好理解了.

function *__gen() {
    console.log('第一個yield的有效范圍開始')
    yield '第一個yield的有效范圍結束'
    
    console.log('第二個yield的有效范圍開始')
    yield '第二個yield的有效范圍結束'
    
    console.log('第三個yield的有效范圍開始')
    yield '第三個yield的有效范圍結束'
    
    console.log('我在第四個yield的有效范圍開始')
    yield '第四個yield的有效范圍結束'
    
}

不包括yield這一行代碼的左邊。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末渡紫,一起剝皮案震驚了整個濱河市到推,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惕澎,老刑警劉巖莉测,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異唧喉,居然都是意外死亡捣卤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門欣喧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腌零,“玉大人,你說我怎么就攤上這事唆阿∫娼В” “怎么了悯森?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵骇笔,是天一觀的道長。 經(jīng)常有香客問我底瓣,道長浅辙,這世上最難降的妖魔是什么扭弧? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮记舆,結果婚禮上鸽捻,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好御蒲,可當我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布衣赶。 她就那樣靜靜地躺著,像睡著了一般厚满。 火紅的嫁衣襯著肌膚如雪府瞄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天碘箍,我揣著相機與錄音遵馆,去河邊找鬼。 笑死丰榴,一個胖子當著我的面吹牛货邓,可吹牛的內容都是我干的。 我是一名探鬼主播多艇,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼逻恐,長吁一口氣:“原來是場噩夢啊……” “哼像吻!你這毒婦竟也來了峻黍?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤拨匆,失蹤者是張志新(化名)和其女友劉穎姆涩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惭每,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡骨饿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了台腥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宏赘。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖黎侈,靈堂內的尸體忽然破棺而出察署,到底是詐尸還是另有隱情,我是刑警寧澤峻汉,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布贴汪,位于F島的核電站,受9級特大地震影響休吠,放射性物質發(fā)生泄漏扳埂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一瘤礁、第九天 我趴在偏房一處隱蔽的房頂上張望阳懂。 院中可真熱鬧,春花似錦、人聲如沸岩调。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽誊辉。三九已至矾湃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堕澄,已是汗流浹背邀跃。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛙紫,地道東北人拍屑。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像坑傅,于是被迫代替她去往敵國和親僵驰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內容