# [IM012] Iterator & Generator
> **前言**
> 2020 秋天,我將用 30 天的時間,來嘗試回答和網路前端開發相關的 30 個問題。30 天無法一網打盡浩瀚的前端知識,有些問題可能對有些讀者來說相對簡單,不過期待這趟旅程,能幫助自己、也幫助讀者打開不同的知識大門。有興趣的話,跟著我一起探索吧!

## Iterator
Iterator 中文稱做迭代器,顧名思義,就是拿來迭代某些東西的工具。迭代這件事情在過去其實很常出現,譬如使用 for loop 或 forEach 來遍歷整個陣列,就是一種迭代的過程。
在 ES6 當中,提供了一個方式讓 JavaScript 當中所有物件都變成是可以迭代的。不過要達到可以迭代的情況,需要遵守下面兩個 protocols:
### Iterable protocol
要成為一個可以迭代的物件,必須實作 `@@iterator` 方法,也就是在物件當中需要有一個 key 為 `@@iterator` 的屬性。而 `@@iterator` 可以透過操作 `[Symbol.iterator]` 來取得。舉例來說:
```javascript=
const obj = {
[Symbol.iterator]: function () {
// return something
}
}
```
而這個 `[Symbol.iterator]` 本身會滿足下面會提到的 Iterator protocol。
### Iterator protocol
這個 protocol 當中則定義了產生一系列迭代所產生的值的規格。當一個 iterator 呼叫 `next` 方法之後,會得到至少以下兩個屬性與值:
* done - 若值為 false,代表後續還有值可以迭代;若為 true,代表迭代結束。
* value - 經迭代而獲得的值
講到這裡,不如直接來看個例子吧。
### Example
```javascript=
const obj = [1, 2, 3, 4, 5]
let iterator = obj[Symbol.iterator]()
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
```
這段程式碼的意思是,一開始先建立一個 obj 物件(其實是個陣列,不過 JavaScript 當中所有東西都是物件)
接著實作 `@@iterator`,方式是執行「透過 `Symbol.iterator` 所回傳的 function」而得到 iterator。這裡其實也會發現,JavaScript 其實早就先把這個 "API" 放到所有物件當中了。
最後,只要我們透過 iterator 呼叫 `next` function,就可以迭代這個物件當中的值。當沒有東西可以迭代的時候, done 的值就變為 true。
舉另外一個例子:
```javascript=
let str = 'td'
let iterator = str[Symbol.iterator]()
iterator.next() // { value: 't', done: false }
iterator.next() // { value: 'd', done: false }
iterator.next() // { value: undefined, done: true }
```
看到這裡可能會想,其實本來陣列和字串就可以迭代了,為什麼要這麼麻煩呢?
的確在 JavaScript 當中有些東西本身不需要特別做什麼事情,就可以迭代了。除了陣列和字串之外,還有
* Map
* Set
* function 當中的 arguments (很像 array 但不是 array 的東西)
* DOM 當中的 NodeList
都可以直接透過 `for...of` 來迭代。不過 `for...of` 本身,其實就是透過上面的方法,呼叫出 iterator 來一步步迭代上面這些物件。
## Build our own iterator
其實一般的物件並沒有定義迭代的順序,因此若開發者要讓自己的物件變成可迭代,就需要自行定義迭代的順序,以及所產生的值。
舉例來說:
```javascript=
const fb = {
[Symbol.iterator](){
let a = 0, b = 1
return {
next() {
let val = { value: b, done: false }
b += a
a = val.value
return val
}
}
}
}
```
這裡我們在物件當中,建立了一個 key 為 `[Symbol.iterator]` 的 function,並會回傳一個 `next` function 提供 iterator 呼叫。這裡我們並沒有建立從外部取得數值或儲存數值的方法,而是直接定義了每次迭代所會產生的值。
執行結果如下:
```javascript=
let iterator = fb[Symbol.iterator]()
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: 8, done: false }
```
其實這就是一個 Fibonacci 數列產生器。
但講到這裡,可能還是不知道為什麼要談 iterator。其實說了這麼多,都是為了介紹下面的 generator 出場
## Generator
generator 實際上就是建構在 iterator 之上,同樣可以透過呼叫 `next` function 來執行或迭代下一步。它長得和一般的 function 很像,只是多了個星號 (*)。
另外,generator 是透過 yield 而不是 return 來回傳數值。譬如
```javascript=
function* numbers(){
yield 1
yield 2
yield 3
}
```
透過 `next` 不斷呼叫下一步,直到沒有回傳值的時候,回傳 `{done: true}` ,像是下面這樣:
```javascript=
const iterator = numbers()
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}
```
更特別是的是,呼叫方可以跟 generator 進行溝通。譬如這裡有另外一個 generator
```javascript=
function* generator(){
let name = yield "what is your name?"
let age = yield "how old are your?"
return `${name} is ${age} years old.`
}
```
接著,我們可以在每次執行的過程中傳入資訊,像是:
```javascript=
const iterator = generator()
iterator.next() // { value: 'what is your name?', done: false }
iterator.next('td') // { value: 'how old are you?', done: false }
iterator.next('18') // { value: 'td is 18 years old.', done: true }
iterator.next() // { value: undefined, done: true }
```
第一次呼叫的時候,會得到第一行 yield 傳出來的 “what is your name?”。
第二次呼叫的時候,我們在 next() 裡面傳入 ‘td’,所以第一行的 yield 收到之後會存入變數 name 當中,並往下執行,傳出 “how old are you?”。
在第三次呼叫的時候傳入 ‘18’,yield 收到之後存入變數 age 當中,然後往下執行。
generator 當中的最後一行是 return,所以直接執行並結束。
## What can we do with generator?
在這篇文章中,我們先看到了 iterator 的特性,接著也看到了建立在 iterator 之上的 generator。雖然在剛剛的過程中,我們自己不斷呼叫 `next` 來達到迭代效果,但其實我們也可以建立一個 function,來代替我們操作 iterator。這樣的 function 有時叫做 `runner`。
另一方面,在使用 generator 的過程中,會發現我們可以自己一步步控制「迭代的過程」,甚至是「傳入參數進而參與迭代的過程」,這些都是無法透過過去常用的迭代方法來完成。
那麼,在什麼情況下會用到 generator 呢?
其中一個就是大家熟悉的 async/await 語法。async/await 本身是個語法糖,背後的運作就是透過 generator 和 promise 所建立起來的。
另一個就是在 Redux-saga 當中,會看到 generator 的出現。
## End
Iterator 和 generator 不是在剛開始學習 JavaScript 的時候就會碰到的概念與工具,不過學到一定程度之後就需要來認識一下,我想在未來設計程式架構、或是理解第三方套件的時候會有幫助。
## Ref
* [Iteration protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)
***
> **TD**
> Be curious as astronomer, think as physicist, hack as engineer, fight as baseball player
>
> [More about me](https://tsungtingdu.github.io/profile/)
>
> *"Life is like riding a bicycle. To keep your balance, you must keep moving."*
***
###### tags: `ironman` `javascript`