第一次看到 generator 時,我只有看到yield
這個關鍵字,以為只是流程控制的機制,後來才聽到 generator(產生器)這個名字,一直以為說的是 factory pattern 那種角色,困惑了起來去查了一些介紹才知道也是很早就有的機制,主要都是用在迴圈上,命名雖然是用 generator(生產器)和 yield(產出),但是不是 factory 那種,generator 其實是用來產生 iterator 的。
Iterator 其實是一組定義好的介面,讓物件可以在迴圈裡面取得整個串列的資料,而在 ES6 裡,可以處理 iterator 的迴圈形式,就是上一篇文章介紹 Map 和 Set 時,有講到的for of
這個新語法,在 ES6 裡面定義的 Iterator 介面其實很簡單,只有定義了一個next
method,每次執行會回傳一個物件,裡面兩個屬性:
{
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)
這個新的運算子也是一個非常好用的新功能。