Es6 generator淺入淺出

ponyflying發表於2019-03-05

生成器

生成器是特殊的函式。生成器函式在執行時能暫停,後面又能從暫停處執行。 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}
複製程式碼

迭代器與生成器互動

互動方式有三種

  1. 向生成器傳遞引數與生成器互動,像普通函式一樣傳遞引數。
function* parGenerator(weapon) {
    yield weapon
}

const parIterator = parGenerator('j-20')
console.log(parIterator.next()) // { value: 'j-20', done: false }
複製程式碼
  1. 通過向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()傳遞引數沒有意義,所以,如果想為生成器提供一個初始值,可以向生成器函式穿第一個引數作為初始值。

  1. 通過迭代器的throw方法與生成器互動
function* throwGenerator() {
  try {
    yield 'good'
  } catch (err) {
    console.log(err)
  }
}

const throwInterator = throwGenerator()
console.log(throwInterator.next())
console.log(throwInterator.throw('bad'))
複製程式碼

thrownext傳參類似,只能對當前生成器yield的值throw,因此,在建立迭代器之後立即呼叫throw沒有意義(會報錯),換個角度來說,只有程式碼執行的時候才可以try-catch,在throw之前沒有執行生成器,try-catch也就沒什麼用。

注意:throw必須在next之後呼叫,因為在呼叫第一個next之後,生成器在第一個yield處掛起,只有在請求下一個值或者throw的時候才會繼續往後執行try...catch...,在throw的時候,生成器重新入棧,從yield 'good'的地方繼續往後執行,生成器會將yield 'good'作為一個錯誤丟擲,被catch抓住。

例項

  1. 生成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)
}
複製程式碼

相關文章