JS:生成器函數和迭代器以前的關係

生成器函數

生成器函數就是在普通函數的 function 關鍵字後面加個星號(*javascript

function* generator() {
    yield 1
    yield 2
    yield 3
}
複製代碼

調用生成器函數,返回的就是一個迭代器(iterator)。html

const iterator = generator()

iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}
複製代碼

迭代器就是部署了 next 方法一個對象,每次調用就會返回一個包含 valuedone 屬性的對象:value 表示當前遍歷值,done 則表示遍歷時候結束。java

for-of 循環

for..of 循環是用來遍歷可迭代對象(iterable)的。語法以下:數組

for (variable of iterable) {
    // statements
}
複製代碼

所謂的可迭代對象就是部署了 [Symbol.iteraor] 屬性的對象,這個屬性是個方法。調用這個方法就能獲得迭代器,所以 [Symbol.iteraor] 還被稱爲「迭代器生成函數」。ide

const arr = ['a', 'b', 'c']

// (1) 使用 for-of 循環
for (const element of arr) {
  console.log(element)
}

// (2) 手動循環
let iterator = arr[Symbol.iterator]()
while (true) {
  let result = iterator.next()
  if (result.done) break
  console.log(result.value)
}
複製代碼

(1) 處的 for-of 循環,內部的執行邏輯如 (2) 處代碼反映的這樣。最終的輸出結果爲 a -> b -> c函數

for-of 與生成器函數

你可能不知道的是:調用生成器函數的結果也能被 for-of 循環遍歷。ui

仍是以上面的 generator() 爲例:this

for (const element of generator()) {
  console.log(element)
}

// 1 -> 2 -> 3
複製代碼

果真能夠。但我不知道看後的你們有沒有疑問,我卻是有。spa

generator() 的結果不是個迭代器嗎,爲何也能被 for-of 調用?莫非 for-of 不只對可迭代對象,也能直接對迭代器遍歷?code

咱們來試試。

for (const element of arr[Symbol.iterator]()) {
  console.log(element)
}
// a -> b -> c
複製代碼

arr[Symbol.iterator]() 返回的是個迭代器,看到確實能夠被 for-of 遍歷。那麼就能說明 for-of 也能對迭代器作遍歷嗎?

for-of 只能遍歷可迭代對象

咱們來看下,arr[Symbol.iterator]() 的結構吧。

image.png

  • 支持 next 方法說明是個迭代器對象。
  • 同時原型鏈上繼承了一個 [Symbol.iterator] 屬性……啊,原來仍是個可迭代對象啊。

數組的 arr[Symbol.iterator]() 的返回對象:既是迭代器,也是可迭代對象。這就有點問題了,由於這裏起做用的可能仍是 [Symbol.iterator] 屬性(按 for-of 的語法來講,是這樣的)。

爲了充分證實,我們再用普通對象改裝成的可迭代對象,證實一下:

let range = {}
range[Symbol.iterator] = function() {
  return {
    current: 1,
    last: 3,
    
    next() {
      if (this.current <= this.last) {
        return { done: false, value: this.current++ }
      } else {
        return { done: true }
      }
    }
  }
}
複製代碼

咱們先用 for-of 遍歷一下:

for (let item of range) { console.log(item) } // 1 -> 2 -> 3
複製代碼

OK,沒問題。下面進入關鍵一步了,對 range[Symbol.iterator]() 的結果遍歷。

for (let item of range[Symbol.iterator]()) { console.log(item) }
// TypeError: range[Symbol.iterator] is not a function or its return value is not iterable
複製代碼

報錯啦!range[Symbol.iterator] 返回的要是個可迭代對象才行

便是說 arr[Symbol.iterator]() 之因此可以遍歷,不是由於它是個迭代器,而是由於它是個可迭代對象。

若是再進一步,咱們還能發現一個有趣的地方:

let iterator = arr[Symbol.iterator]()

iterator[Symbol.iterator]() === iterator // true
複製代碼

arr[Symbol.iterator]() 是個可迭代對象,咱們對它調用 [Symbol.iterator],發現結果與自身相等!這也是爲何前面會給咱們形成 for-of 也能遍歷迭代器假象的緣由。

生成器函數返回值仍是個可迭代對象

再回到前面生成器函數的例子:

function* generator() {
    yield 1
    yield 2
    yield 3
}

for (const element of generator()) {
  console.log(element)
}
// 1 -> 2 -> 3
複製代碼

generator() 的返回結果是個迭代器對象,但這個對象能被 for-of 循環,絕對是由於它同時仍是個可迭代對象!

咱們驗證下:

image.png

果真是的。一樣的:

let iterator = generator()

iterator[Symbol.iterator]() === iterator // true
複製代碼

啊哈,同爲可迭代對象的迭代器,調用 [Symbol.iterator] 後,結果與自身相等(再摸一遍腦門)——這就是爲何,前面咱們會有 for-of 也能遍歷迭代器幻象的緣由!

使用生成器函數建立可迭代對象

有一個有趣的用例,咱們改下下上面的 range 對象:

const range = {
    *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;
    }
}

for (let value of range) { 
    console.log(value)
}
// 1 -> 2 -> 3
複製代碼

這裏使用的是「生成器函數的返回值是迭代器」這一特色。

總結

  • 調用生成器函數返回的結果,是個迭代器對象(具備 next 方法)。同時,
  • 這個迭代器仍是個可迭代對象。這就是爲何
  • generator() 的結果能夠被 for-of 遍歷的緣由!

參考連接

(正文完)


廣告時間(長期有效)

我有一位好朋友開了一間貓舍,在此幫她宣傳一下。如今貓舍裏養的都是布偶貓。若是你也是個愛貓人士而且有須要的話,不妨掃一掃她的【閒魚】二維碼。不買也沒關係,看看也行。

(完)

相關文章
相關標籤/搜索