---
title: JavaScript 生成器之精髓
---

迭代器/生成器是一组用以做自动化流程控制的语法对象。其核心价值点有二：**可自动化的流程设计与控制**、**可暂停特性**。其他所谓价值都是纸老虎。

## 目录

* 迭代器 - iterator
* 生成器 - generator

## 迭代器 - iterator

迭代器和生成器是你天天在用但又感知不到的东西。生成器是用来生成迭代器的东西，其基础还是迭代器。让我们先从迭代器入手。

迭代器是一类**特殊对象**，它具有用来**访问迭代过程**的**专用接口**。迭代者，循环也。可以这样理解，它是专门用来处理循环过程的一类接口，最终达到简化循环的目的。任何具备这类接口特征的对象，都认为具有了迭代器的特征。先来看看手写循环过程的痛点在哪里：

```javascript
const colors = ['blue', 'green', 'red']
for (let i = 0; i < colors.length; i++) {
  doSomething(colors[i])
}
```

这可能是你每天都会写的代码。但看完这篇文章后，希望这样的代码不要再出现于你的手下。2018 年，不应该再用这么命令式的方式写代码了。命令式有什么毛病呢？

1.  对于每类数组，其循环结构的模板代码都是重复的。代码量较大，重复多，不优雅
2.  需要手动管理索引，以获得对应元素的值
3.  对于多重循环，不同层之间的索引值可能相互引用修改，难以调试

相反，我们至少应该以更加「声明式」一些的思路来写。比如，以下的方式：

```javascript
for (color of colors) {
  doSomething(color)
}
```

这是 ES6 提供的 `for..of` 语法。当然，你可以说它就是把循环过程封装在关键字下，但试想一下，如果它处理的不是数组呢？你就不能以索引的方式来访问元素了，对不？你也就不能用 `length` 的方式来判断循环结束了，对不？于是我们想到，为了使我们对命令式循环的处理流程更加通用，我们需要为每一类所谓的「可迭代对象」编写一个具有统一接口的迭代器，然后就能在 `for..of` 底下基于一套相同的迭代器接口来访问它们了。

一个典型的迭代器，主要就是要提供一个 `next` 接口，这个接口需要回答两个问题：下一个元素的值是啥，以及是否还有下一个元素。

```javascript
function createArrayIterator(items) {
  let index = 0
  return {
    next() {
      const done = index >= items.length
      const value = done ? undefined : items[index++]

      return { value, done }
    },
  }
}
```

在 ES6 中，这个迭代器函数被保存到原型上的 `[Symbol.iterator]` 键上。**也即是说，所有提供了这个键的对象，都能被当成可迭代对象被访问**（如 `for..of` 运算符、展开运算符 `...object` 等）。JS 中内建类型的可迭代对象分别有：

|              内建类型               |             迭代器              |
| :---------------------------------: | :-----------------------------: |
| `Array.prototype[Symbol.iterator]`  |      `[Function: values]`       |
|  `Set.prototype[Symbol.iterator]`   |      `[Function: values]`       |
|  `Map.prototype[Symbol.iterator]`   |      `[Function: entries]`      |
| `String.prototype[Symbol.iterator]` | `[Function: [Symbol.iterator]]` |

然而呢，到目前为止，这个东西看起来还没什么价值。除了，它本身是一种十分常见的编程模式；除了，可以为你自定义的对象定义迭代器……好像就没啥价值了。

## 生成器 - generator

生成器函数。生成什么的函数呢？是生成迭代器的一个函数，它会返回一个迭代器，只不过用它生成的迭代器，除了可以 `next()`，还可以 `throw()`。一方面，可以用它来方便地生成一些迭代器；但它最大的价值，还在于 `yield` 语句提供了暂停执行的能力。只有在被 `next()` 的时候，它才会接着往下执行。

这里有两个重点：一个是，被谁 `next()`？一个是，这种暂停执行的能力有什么价值？

不管是被谁 `next()`，它都应该是一个**自动化的程序**。抽象出迭代器接口，以及自动化创建迭代器的生成器函数，目标无非是为了更灵活、更自动化的流程控制。因此，只有当这个「流程控制」具备自动化的能力时，生成器和迭代器函数才显出其价值。在语言规范中，称这两者为 Control Abstraction（流程）控制抽象层，也是这个道理。这是学习生成器质问其价值的一个重要回答。也就是说，很多其他（比如知乎上讲的）作用都不是非生成器不可的价值。

其次是，这种暂停执行的能力有什么价值？目前看到的就是实现异步流程的同步化（`async` / `await`），以及 `redux-saga` 这样的应用。其他还没看到什么特别的价值。有些人说，支持「懒求值」，这个事情我看来好像就是瞎扯，因为如果 generator 函数最终被自动化，你哪里有手动「懒」一把的地方？还不是一把全执行完。当然，可能是我没有理解到它的场景。

总结起来，什么时候可以考虑用生成器技术呢，两个：

* 需要实现一套**可自动化**的**流程控制**程序时
* 需要利用**暂停执行**的能力时
