Es6 generator淺入淺出
生成器是特殊的函式。生成器函式在執行時能暫停,後面又能從暫停處執行。 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: 'm16', done: false} console.log(weaponIterator.next()) // {value: 'h58', 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) } 複製程式碼