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()
同樣的代碼放在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
的英文意思是:放棄
用一張圖去理解的話:
也就是說,每次調用 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}
那第一個例子,單純的使用 yield
,后面沒有接任何表達式,返回的對象的value
是undefined
嗎?
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}
也就是說,
next()
會返回一個對象,對象的.value
屬性就是yield
后面表達式返回的值.
yield可以返回值:next()==>{value:yield表達式,done:boolean}
yield
關鍵字,將 generator
中斷,并返回yield
后面表達式的值賦值給next(){value屬性}
(如果有),
并等待下一次的 next()
關于 generator 里寫 return 關鍵字
我們發(fā)現(xiàn)在 generator
中,如果有 n
個 yield
,我們就需要手動的調用 n+1
次 next()
才能得到 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()
方法每次回返回一個對象,包括 value
和 done
兩個屬性.
其中,value
是 yield
后面表達式的值. 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())
關于 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'
a
是 hello
嗎?
我們之前說過,yield
后面返回的值,給next()
返回的對象的.value
了.
所以,這里的 a
是不是 hello
不好說.
同理 b
是不是 world
也不好說.
按照我們以前的理解, let xx = xxx
都是把表達式右邊的結果返回給左邊.
在這里也會是這樣嗎?
突然發(fā)現(xiàn),之前的想法全是錯的.出來的是
undefined,undefined
那 hello
,world
哪去了?
記得之前說的嗎?
yield
的返回值,給對象了.
console.log(g.next())
console.log(g.next())
console.log(g.next())
所以,let a = yield 'hello'
這里的 a
和后面的 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)
所以,先不管 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,我滴媽啊.那第一個g.next(1)傳遞的1哪去了?????
總是有驚喜.....
然而實際情況是:
-
每次調用next()
方法,都會執(zhí)行到一個yield
結束.- 返回
yield
后面的表達式給對象的.value
(如果有) - 代碼暫停,等待下一次的
next()
- 返回
上面只說了表達式右邊的yield
情況,對左邊的let x
沒有絲毫提起.
其實這也是為什么 1
消失了的原因.
使用大神們的博客說明:
上面介紹的next方法均沒有參數(shù)艳悔,其實可以為它提供一個參數(shù)集嵌。參數(shù)會被當作上一個yield語句的返回值。如果沒有參數(shù)放吩,那默認上一個yield語句的返回值為undefined智听。
反正我是看起來比較費勁.
一張我自己理解的圖來說明為什么 1
消失了.
回憶以下之前說的,每一次執(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這一行代碼的左邊。