生成器
生成器是特殊的函式。生成器函式在執行時能暫停,後面又能從暫停處執行。 show me the coding
function* zcGenerator() { // 這就是生成器
yield 'ak-47'
}
複製程式碼
迭代器
const zcInterator = zcGenerator()
複製程式碼
呼叫生成器並不會執行函式,而是會返回一個迭代器,迭代器用來控制生成器的執行。呼叫迭代器的next()
方法,會向生成器請求一個值,生成器內的語句執行到第一個yield
的位置,並返回yield
後跟的值,此時生成器非同步掛起執行,直到下次再請求值,生成器恢復執行,如此迴圈下去,直到最後生成器執行完畢。
next()
方法返回一個物件{value: '', done: ''}
, value
表示本次yield
返回的值,done
表示生成器後續時候還是有yield語句,即生成器是否執行完畢。
從執行上下文的角度來理解迭代器,建立迭代器的時候,生成器入棧,初始化引數(如果有的話),執行完畢後出棧,並且銷燬當前執行環境,但是迭代器仍然儲存著對他的引用,所以該環境並不會銷燬,這個東西和閉包很像。換個角度來說,生成器需要恢復執行,所以環境不能銷燬,儲存在迭代器上。
function* weaponGenerator() {
yield 'ak47'
yield 'm16'
}
const weaponIterator = weaponGenerator()
console.log(weaponIterator.next()) // {value: 'ak47', done: false}
console.log(weaponIterator.next()) // {value: 'm16', done: false}
console.log(weaponIterator.next()) // {value: undefined, done: true}
複製程式碼
使用yield*
表示將執行權交到另一個生成器函式(當前函式暫停執行)
function* weaponGeneratorInner() {
yield 'g56'
yield 'h58'
}
function* weaponGenerator() {
yield 'ak47'
yield* weaponGeneratorInner()
yield 'm16'
}
const weaponIterator = weaponGenerator()
console.log(weaponIterator.next()) // {value: 'ak47', done: false}
console.log(weaponIterator.next()) // {value: 'g56', done: false}
console.log(weaponIterator.next()) // {value: 'h58', done: false}
console.log(weaponIterator.next()) // {value: 'm16', done: false}
console.log(weaponIterator.next()) // {value: undefined, done: true}
複製程式碼
迭代器與生成器互動
互動方式有三種
- 向生成器傳遞引數與生成器互動,像普通函式一樣傳遞引數。
function* parGenerator(weapon) {
yield weapon
}
const parIterator = parGenerator('j-20')
console.log(parIterator.next()) // { value: 'j-20', done: false }
複製程式碼
- 通過向
next()
傳遞引數,與生成器互動next()
的引數會傳遞給當前生成器正在yield
的值,因此,第一次呼叫next()
,生成器並沒有處於掛起的值,所以傳遞引數沒有任何意義,呼叫完第一個next()
方法,生成器開始執行,遇見第一個yield
,函式掛起,並且返回當前值;第二次呼叫next('j-30')
,j-30
會替換掉當前yield
的值j-20
,並繼續往下執行,將newWeapon
賦值為j-30
,生成器執行完畢。
function* parGenerator(weapon) {
const newWeapon = yield weapon
return newWeapon
}
const parIterator = parGenerator('j-20')
console.log(parIterator.next()) // { value: 'j-20', done: false }
console.log(parIterator.next('j-30')) // { value: 'j-30', done: true }
複製程式碼
注意:因為通過
next()
傳遞引數只能傳遞給當前生成器yield
的值,因此第一次呼叫next()
傳遞引數沒有意義,所以,如果想為生成器提供一個初始值,可以向生成器函式穿第一個引數作為初始值。
- 通過迭代器的
throw
方法與生成器互動
function* throwGenerator() {
try {
yield 'good'
} catch (err) {
console.log(err)
}
}
const throwInterator = throwGenerator()
console.log(throwInterator.next())
console.log(throwInterator.throw('bad'))
複製程式碼
throw
與next
傳參類似,只能對當前生成器yield
的值throw
,因此,在建立迭代器之後立即呼叫throw
沒有意義(會報錯),換個角度來說,只有程式碼執行的時候才可以try-catch
,在throw
之前沒有執行生成器,try-catch
也就沒什麼用。
注意:
throw
必須在next
之後呼叫,因為在呼叫第一個next
之後,生成器在第一個yield
處掛起,只有在請求下一個值或者throw
的時候才會繼續往後執行try...catch...
,在throw的時候,生成器重新入棧,從yield 'good'
的地方繼續往後執行,生成器會將yield 'good'
作為一個錯誤丟擲,被catch
抓住。
例項
- 生成id
function* idGenerator() {
let id = 0
while (true) {
yield ++id
}
}
const idIterator = idGenerator()
const id1 = idIterator.next().value
console.log(id1)
const id2 = idIterator.next().value
console.log(id2)
複製程式碼
因為yield會暫時掛起函式執行,所以while
不會無限迴圈
2. 遍歷dom節點
function* elementGenerator(element) {
yield element
element = element.firstElementChild
while (element) {
elementGenerator(element)
element = element.nextElementSibling
}
}
for (let elementGeneratorElement of elementGenerator()) {
console.log(elementGeneratorElement.nodeName)
}
複製程式碼