---
# System prepended metadata

title: '[IM012] Iterator & Generator'
tags: [javascript, ironman]

---

# [IM012] Iterator & Generator

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

![](https://images.unsplash.com/photo-1580576187507-308774c3f12b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80)

## 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`