ES6 的 Generator 與 Iterator

第一次看到 generator 時,我只有看到yield這個關鍵字,以為只是流程控制的機制,後來才聽到 generator(產生器)這個名字,一直以為說的是 factory pattern 那種角色,困惑了起來去查了一些介紹才知道也是很早就有的機制,主要都是用在迴圈上,命名雖然是用 generator(生產器)和 yield(產出),但是不是 factory 那種,generator 其實是用來產生 iterator 的。

Iterator 其實是一組定義好的介面,讓物件可以在迴圈裡面取得整個串列的資料,而在 ES6 裡,可以處理 iterator 的迴圈形式,就是上一篇文章介紹 Map 和 Set 時,有講到的for of這個新語法,在 ES6 裡面定義的 Iterator 介面其實很簡單,只有定義了一個nextmethod,每次執行會回傳一個物件,裡面兩個屬性:

{
    value: 100, // 下一個元素的值
    done: false // Iterator 是否跑完了
}

value就是迴圈要的值,done則是用來判斷迴圈是否該結束了,generator 就是用yield這個語法來讓你簡單的可以產生 iterator,在 ES6 裡面的語法還算簡單,就是宣告 function 時加個*

function* idMaker(){
    var index = 0;
    while(true)
        yield index++;
}

上面就是一個簡單的 generator,執行idMaker這個 generator function 才會回傳對應的 iterator:

var gen = idMaker();

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

當然這是一個不會結束的 iterator 就是了,使用時要小心不要直接把這種東西丟到迴圈裡面。最後要來介紹的是 iterable 介面,其實如果直接把 iterator 丟給for of是不能用的,要是有支援 iterable 介面的物件才可以,ES6 內建有 iterable 介面的物件型態包括了:

  • 字串
  • 陣列
  • Generator
  • Map, WeakMap
  • Set, WeakSet
  • arguments

這些形式的資料都可以直接用for of迴圈來跑,然後當然,介面都已經定義出來了,表示我們也可以自己寫一個物件來用,iterable 的定義也很簡單,就是把該物件 iterator 的 generator 放在 "@@iterator" 這個屬性下,由於 iterator 只能用一次,所以每次需要都要用 generator 產生一個新的 iterator。另外文件有提到說 "@@iterator" 就是Symbol.iterator這個環境變數,不過我目前測試還不支援的樣子(Symbol 目前還在變動中,以後會在介紹):

var myIterable = {}
myIterable["@@iterator"] = () => (function*(){
    yield 1;
    yield 2;
    yield 3
})();

for (let value of myIterable) {
    console.log(value);
} // 1, 2, 3

通常會把 "@@iterator" 屬性放到 prototype 下比較保險。而有了自定義 iterable 物件的能力,就可以有很多東西可以玩啦,可以拿來跑二元樹、DOM tree、三維陣列或是特定應用領域的資料結構等等。

另外和 iterable 相關的東西還有一個,就是新的 spread 運算子...,它可以把 iterable 物件展開,然後放到像是陣列或是參數、destructuring 等等:

[...myIterable]; // [1, 2, 3]

myFunc(...myIterable); // myFunc(1, 2, 3)

這個新的運算子也是一個非常好用的新功能。