【JavaScript】イテレーターって何？

# イテレーターとは
イテレーターは簡単に言うと、**何かを順番に返す**オブジェクトです。
これは特別な構文やコンストラクタで作るものではなく、特定の条件を満たすオブジェクトをイテレーターと呼びます。

イテレーターは`next`メソッドを持ちます。
このメソッドは呼ばれるたびに以下のプロパティを持つオブジェクトを返す必要があります。

- `value`: 返したい値（何を入れてもOK）
- `done`: 返される値はこれで全てかどうかを示す`boolean`

## 例: 0~9を返す
例えば、0~9までの値を順番に列挙するイテレーターを考えてみます。

まず、イテレーターはオブジェクトなため、新しくオブジェクトを定義します。

```js
const numberIterator = {}
```

次に、イテレーターには`next`メソッドが必要です。
`next`メソッドは`done`と`value`プロパティを持つオブジェクトを返す必要があります。

```diff_javascript
const numberIterator = {
+ next() {
+   return { done: false, value: 0 } // 仮
+ }
}
```

そして、`next`メソッドは呼び出しごとに`value`がインクリメントされている必要があります。
これにはインクリメント用の値を一時的に保管する場所が必要ですが、今回は特に工夫せず`current`というプロパティに置いておきます。

```diff_javascript
const numberIterator = {
+ current: 0,
  next() {
+   return { done: false, value: this.current++ }
  }
}
```

最後に、`this.current`が`10`以上になった後は`{ done: true }`を返すようにします。

全体像はこちらになります。
```js
const numberIterator = {
  current: 0,
  next() {
    if(this.current >= 10) {
      return { done: true } // valueは省略可能
    }
    return { done: false, value: this.current++ }
  }
}
```

なお、この`next`メソッドは呼び出すと以下のように値が返ってきます。

```js
numberIterator.next()
> { done: false, value: 0 }
numberIterator.next()
> { done: false, value: 1 }
numberIterator.next()
> { done: false, value: 2 }
// 略
numberIterator.next()
> { done: false, value: 9 }
numberIterator.next()
> { done: true }
```

## 配列でよくない？
何か連続したものを表現したいなら、配列で事足りると思うかもしれません。
実際大半のイテレーターは配列で表現できますが、表現できないものもあります。

それは、**無限に長さがあるもの**の表現です。

> すべてのイテレーターを配列として表現できるとは想像するのは容易ですが、これは真実ではありません。配列は完全に割り当てなければなりませんが、イテレーターは必要なだけで消費されるため、0 から Infinity までの整数の範囲など、無限のサイズのシーケンスを表現できます。- [MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Iterators_and_generators#iterables)

### 例

先ほどの引用にある、`0`から`Infinity`までの整数をイテレーターで表現してみます。
といっても特別な処理は必要なく、むしろ先ほどのコードから上限を削除するだけです。

```js
const infinityIterator = {
  current: 0,
  next() {
    return { done: false, value: this.current++ }
  }
}
```

たったこれだけで、無限の長さのイテレーターを作れます。

また、イテレーターや後述のイテラブルは**遅延評価**の実装にも使えます。
[こちらの記事](https://qiita.com/access3151fq/items/1b485e2c8cba133bc482)で紹介されているメソッドを使ったり、専用の処理を自作するなどでできます。

# イテラブル
イテラブルは`[Symbol.iterator]`メソッドを持つオブジェクトです。
これは反復可能オブジェクトとも呼ばれます。

`[Symbol.iterator]`メソッドは**イテレーターを返す**必要があります。

:::note
`Symbol.iterator`は[ウェルノウンシンボル](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol#ウェルノウンシンボル)のひとつです。

ウェルノウンシンボルは`Symbol`オブジェクトの静的プロパティに入っている`Symbol`オブジェクトのことで、使うとJavaScriptの組み込み動作をカスタマイズすることができます。

例えば今回の`Symbol.iterator`は、オブジェクトをイテラブルというプロトコルに従わせることで、後述の「イテラブルが使える構文」が使えるようになります。
:::

なお、組み込みのイテラブルなオブジェクトもあります。
例えばこのようなものがあります。

- `Array`
- `Map`
- `Set`

## `[Symbol.iterator]`の実装
例として1~10を示すイテラブルなオブジェクトを作ってみます。

こういったときには**クロージャ**のような実装ができます。

```js
const iterable = {
  [Symbol.iterator]() {
    let current = 0 // 数値を一時保管する変数
    return { // イテレーターを返す
      next() {
        if(current >= 10) { // 10以上かチェック
          return { done: true }
        }
        return { done: false, value: current++ }
      }
    }
  }
}
```

実装手順はこのような感じです。

1. `[Symbol.iterator]`メソッドを実装する
1. メソッド内で数字を一時的に保管する変数（`current`）を宣言する
1. `[Symbol.iterator]`がイテレーター（`next`メソッドを持つオブジェクト）を返すようにする
1. イテレーターの`next`メソッドで`current`を更新する
1. `current`が`10`以上かをチェックし、適切なオブジェクトを返す

## イテラブルが使える構文
オブジェクトをイテラブルにするメリットの一つとして、**いくつかの構文が使える**ようになることが挙げられます。

イテラブルなオブジェクトは以下の構文が使えます。

### [`for...of`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for...of)
各値を使ってループしたいときに使えます。
構文は`for (const 変数名 of イテラブル) { 処理 }`です。

例えば、先ほどの`iterable`を使ってループしてみます。
```js
for (const value of iterable) {
  // 処理
  console.log(value)
}

// 1
// 2
// 略
// 9
// 19
```

`for...of`は配列の要素を使ってループしたいときにも使えますが、これは配列がイテラブルだからです。

### [スプレッド構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)

`...`を使った構文です。
イテラブルなオブジェクトを配列（`Array`）に変換する時に使えます。

```js
const array = [...iterable]
array // [1, 2, 3, ..., 9, 10]
```

また、関数の呼び出し時に引数を展開することもできます。
詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)をご覧ください。

### その他
ここで紹介した以外にも、以下のような構文や言語仕様があります。

- [`yield*`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/yield*)
- [配列の分割代入](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
- （[Iterator Helpers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/Iterator)）

# 参考

https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Iterators_and_generators

https://qiita.com/kura07/items/cf168a7ea20e8c2554c6

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol