「ざっくり」iteratorとgeneratorを理解する

まとめ。

- iterableオブジェクトとは、内部でiteratorオブジェクトを生成して反復可能な状態（`for...of`で回したりできる）にできるオブジェクトのこと
- iteratorオブジェクトは、自身が持つgenerator関数から作られる
- generator関数は`[Symbol.iterator]`をキー名にしなければならない
- generator関数内では`yield`という新しいキーワードがあり、`return`のように処理を中断するが、次に同じ関数がが呼ばれた時に最後に中断した行から処理が再開されるという動きをする
- 独自のiterableオブジェクトは簡単に作れる


## iterable（イテラブル）とは

ざっくりとしたイメージだと

- `for...of`で回るもの
- `Array`
- `HTMLCollection`
- `NodeList`
- `Map`
- `String`
- `Set`
- その他、連番で値が管理されているもの（あくまでもイメージ）

といった具合です。教科書的な説明をするなら、

- 反復可能であるということ
- generator関数を`[Symbol.iterator]`というキーに持っているということ

あたりでしょうか。
オブジェクトがiterableかを判別するには、基本的には`[Symbol.iterator]`プロパティが関数になっているかで確認できます。

```javascript
const arr = [0, 1, 2];
const str = 'hoge';
const obj = {a: 1, b: 2, c: 3};

console.log('Is iterable? => ' + (typeof arr[Symbol.iterator] === 'function'));
console.log('Is iterable? => ' + (typeof str[Symbol.iterator] === 'function'));
console.log('Is iterable? => ' + (typeof obj[Symbol.iterator] === 'function'));
```

## iterator（イテレータ）とは

iterableなオブジェクトの中で使われるオブジェクトのこと。
iterableなオブジェクトが連続して順番に値を返すための機能を持ち、generator関数によって生成されます。

具体的には次のような、`next()`メソッドを持つオブジェクトのことです。

```ts
type iterator = {
  next(): {
    done: boolean,
    value: any | undefined,
  }
}
```

- `next()`は呼ばれるたびに何か新しい値を`value`に入れて返します（返す値がなくなったら`undefined`）
- `done`プロパティは、返す値がなくなった場合`true`になります（デフォルトは`false`）


## generator（ジェネレータ）とは

iteratorオブジェクトを作るための関数です。
次のようなルールでgenerator関数を作成できます。通常の関数と**かなり振る舞いが違う**ことに注意してください。

- `function`のあとに`*`を書く
- `yield`キーワードを使う

### yield（イールド、イェルドゥ）とは

- generator関数の中で`return`文の代わりに使う
- `yield`キーワードの隣に返したい値を記述する
- 一度読み込まれた`yield`は同じiteratorオブジェクトでは**決して読まれない**

#### 実際に動かしてみる

```javascript
/** generator関数です */
const generator = function* () {
  yield 'h';
  yield 'o';
  yield 'g';
  yield 'e';
};
/** iteratorオブジェクトです */
const iterator = generator(); // generatorからiteratorを生成

// 決められたルールで動かすと、値を順番に返します（反復可能な状態）
console.log(iterator.next().value); // > h
console.log(iterator.next().value); // > o
console.log(iterator.next().value); // > g
console.log(iterator.next().value); // > e
console.log(iterator.next().value, iterator.next().done); // > undefined true
```

変数`iterator`には、前述の通り`next()`メソッドを持つiteratorオブジェクトが入っています。
`next()`メソッドは実行されると、次のような構造を持つオブジェクトを返します（1回目）。

```js
{
  done: false,
  value: "h"
}
```

- `done`は、すべての`yield`が読み込まれたら`true`になる
- `value`は、`next()`が呼ばれた順に先頭から`yield`の返り値が入ってくる

最終的には次ような値を返すようになります。
つまり、１度回りきったiteratorオブジェクトは、何も返さなくなるということです。

```js
{
  done: true,
  value: undefined,
}
```
> 一度読み込まれた`yield`は同じ`iterator`では決して読まれない

このような仕組み上、iteratorオブジェクトは、ループさせるたびに新しいものを生成する必要があります。
ちなみに、iteratorオブジェクト自体もiterableです。

```javascript
/** iteratorオブジェクトです */
const iterator2 = generator();

console.log([...iterator2]); // > [h, o, g, e]
console.log([...iterator2]); // > []
```

### yield*式

`yield*`式は、いっぱい`yield`を書かなくてもよくなるものというイメージで最初は良いと思います。
正確には、iteratorが呼ばれ時に参照するオブジェクトを別のiterableオブジェクトで代替する（別のオブジェクトに委任する）ためのものです。

```javascript
const generator = function* () {
  yield* 'hoge'; // Stringはiterable
};
const iterator = generator(); // generatorからiteratorを生成

console.log(iterator.next().value); // > h
console.log(iterator.next().value); // > o
console.log(iterator.next().value); // > g
console.log(iterator.next().value); // > e
console.log(iterator.next().value, iterator.next().done); // undefined true
```

または

```javascript
const generator = function* () {
  yield* ['h', 'o', 'g', 'e']; // Arrayはiterable
};
const iterator = generator(); // generatorからiteratorを生成

console.log(iterator.next().value); // > h
console.log(iterator.next().value); // > o
console.log(iterator.next().value); // > g
console.log(iterator.next().value); // > e
console.log(iterator.next().value, iterator.next().done); // undefined true
```

## generator関数内の変数は計算内容が継続される？

`yield`文はgenerator関数（正確にはiterator）が呼ばれた順番に値を返すのと同時に**中断と再開**の役割をもっています。

```javascript
const generator = function* () {
  console.log(0);
  yield 'h';
  console.log(1);
  yield 'o';
  console.log(2);
  yield 'g';
  console.log(3);
  yield 'e';
  console.log(4);
};
const iterator = generator(); // generatorからiteratorを生成

console.log(iterator.next().value); // > 0 h
console.log(iterator.next().value); // > 1 o
console.log(iterator.next().value); // > 2 g
console.log(iterator.next().value); // > 3 e
console.log(iterator.next().value, iterator.next().done); // > 4 undefined true
```

`yield`の間に挟まっている`console.log()`がiteratorの呼び出しに応じて走っていることから、generator関数内のコードの途中から処理が再開している様子がわかります。

`yield`文で中断された計算内容がgenerator関数では保持されているように見えますね。

次の例を見てみましょう。

```javascript
// ただの関数
const foo = function () {
  let index = 0;

  while (index < 3) {
    return index++; // returnしたあとにインクリメント
  }
};

// 何度やっても 0 になる。当たり前。
console.log(foo()); // > 0
console.log(foo()); // > 0
console.log(foo()); // > 0
```

```javascript
// generator関数
const foo = function* () {
  let index = 0;

  while (index < 3) {
    yield index++; // yieldしたあとにインクリメント
  }
};
const iterator = foo(); // ★

// whileの途中のindexの計算値が帰ってくる
console.log(iterator.next().value); // > 0
console.log(iterator.next().value); // > 1
console.log(iterator.next().value); // > 2
```

実は実際のところ、generator関数の処理が中断・再開されているように感じているだけなのですが、最初は驚くかもしれません。

勘違いしやすいかもしれないですが、「★」の行でgenerator関数は1度実行され、別のオブジェクトを生成しています。**この時点で`index`のインクリメントも、`while`文も計算し終わっています**。

もし改めてgenerator関数`foo()`から新しいiteratorオブジェクトを作れば、それはすでに作ってあるiteratorオブジェクトとは関係がないので再び変数`index`の値は`0`から始まります。

## 独自のイテラブルオブジェクトを作る

あとからオブジェクトを反復可能にするには、次のようにgenerator関数を生やします。

```javascript
const obj = {}; // ただのオブジェクト

obj[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

const iterator = obj[Symbol.iterator](); // 手動でiteratorを作る

// 手動で呼び出し
console.log(iterator.next().value); // > 1
console.log(iterator.next().value); // > 2
console.log(iterator.next().value); // > 3
console.log(iterator.next().value, iterator.next().done); // > undefined true

// for...ofが初期化から呼び出しまで自動でやってくれている
for (const item of obj) {
  console.log(item); // > 1, 2, 3
}
```

初回のオブジェクト定義の段階でも定義できます。

```javascript
const obj = {
  * [Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
  hoge: 'piyo',
  foo() {
     return 100;
  }
};

for (const item of obj) { 
    console.log(item); // > 1, 2, 3
}
```

`class`で定義する場合も同様です。

```javascript
class Hoge {
  * [Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
}

for (const item of new Hoge()) { 
    console.log(item); // > 1, 2, 3
}
```


## Example

たとえば、`NodeList`を`for..of`で回してみましょう。

```javascript
const anchors = document.querySelectorAll('a');

for (const item of anchors) {
   console.log(item);
}
```

普通に回ったと思います。

次に、この`NodeList`のiteratorを破壊して別の値が出るようにしてみます。

```javascript
// 適当な文字列でiteratorを破壊
const anchors = document.querySelectorAll('a');

anchors[Symbol.iterator] = function* () {
  yield* ['a', 'b', 'c'];
};

for (const item of anchors) {
   console.log(item);
}
```

`a`要素が入った`NodeList`を`for...of`したはずなのに、「a」「b」「c」が出力されました。
`yield*`式ではなく`yield`で行う場合は次のようにも書けます。

```javascript
// 適当な文字列でiteratorを破壊
const anchors = document.querySelectorAll('a');

anchors[Symbol.iterator] = function* () {
  const arr = ['a', 'b', 'c'];
  let index = 0;

  while (index < arr.length) {
    yield arr[index++];
  }
};

for (const item of anchors) {
   console.log(item);
}
```

もともとの`NodeList`のiteratorを破壊しつつも、同じ振る舞いをするように書き換えると、次のように書けます（非常に無意味ですが）。

```javascript
// 標準のiteratorと同じこと
const anchors = document.querySelectorAll('a');

anchors[Symbol.iterator] = function* () {
  let index = 0;

  // generator関数内の`this`は、最終的にiteratorを持つオブジェクトを参照します。
  while (index < this.length) {
    yield this[index++];
  }
};

for (const item of anchors) {
   console.log(item)
}
```

## おまけ１：iterableになったオブジェクトはスプリット構文が使える

不思議な感じがしますが、`[object Object]`も配列として分割できます。
スプリット構文もiterableかどうかが重要だったんですね。

```javascript
const obj = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
  hoge: 'piyo',
  foo() {
     return 100;
  }
};

console.log(...obj); // > 1 2 3
console.log([...obj]); // > (3)[1, 2, 3]
```

なお、反復可能になったオブジェクトも、次のような分割代入ではもともとの振る舞いをします。

```javascript
{
  ...obj, // hoge と foo() が展開される
}
```


## おまけ２：jQueryオブジェクトをiterableにする[^1]

**追記（2019/12/10）：jQueryの最新バージョンではもともとjQueryオブジェクトがiterableでしたorz**

jQueryでは、要素を常に連番で管理します。jQuery内では`.get(0)`メソッド[^2]や`.eq(0)`メソッド[^3]で個別に取得するのが一般的な方法ですが、ブラケッツ（各括弧）で連番を入れても該当する1つの要素を取ることができます。

```javascript
console.log($('a')[0]); // 最初のa要素
```

一見`NodeList`や`HTMLCollection`のようなふるまいですが、実はどちらでもありません。キー名が連番のため配列のように見えるオブジェクト、これをjQueryオブジェクトと呼んだりします。

jQueryオブジェクトはiterableではありません。`for...of`に無理やり入れると、次のようにエラーが吐かれます。

```javascript
const $a = $('a');

for (const elm of $a) { // > $a is not iterable
  console.log(elm);
}
```

これまで見てきた方法でjQueryオブジェクトにiteratorを生やしてみます。

```javascript
const $a = $('a');

$a[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

for (const elm of $a) {
  console.log(elm); // > 1 2 3
}
```

とりあえず回りました。あとは１つ１つ`yield`が正しくjQueryの持つ要素を返せるようにgeneratorの中身を書き換えて、jQueryの大元に生やすだけです。

```javascript
$.prototype[Symbol.iterator] = function* () {
  let index = 0;
  const {length} = this; // thisはjQueryオブジェクトを指します

  while (index < length) {
    yield this[index++];
  }
};

for (const elm of $('a')) {
  console.log(elm); // 要素が1つずつコンソールへ出力される
}
```

無事に回すことができました。とはいえ、これは練習で回せただけだということにご注意ください。
現実的にはjQueryにはほぼ同じ振る舞いをする`.each()`メソッドが用意されていますので、jQueryオブジェクトを回したいときにはこちらを利用したほうがいいですね（IEサポートもありますし）。

## まとめ

そんな感じで、任意のオブジェクトに自分の意図した値を渡しながら反復処理をさせられるのがiteratorとgeneratorです。

- もっと見る機能
- 重複せずに値を1つずつ返す機能（おみくじとか阿弥陀籤とか？）
- もともとイテラブルなオブジェクトを継承せずに、`class`から生まれたインスタンスを反復可能にしたい時

とかに使えそうですね👀

うまく使いこなせれば、簡潔で一貫性のある見渡しのいいコードが書けるようになるかもしれない重要な仕組みなので、しっかり押さえておきたいです🥒


[^1]: jQueryが読み込まれている環境前提
[^2]: 返り値は`0`番目の要素オブジェクト
[^3]: 返り値は`0`番目の要素だけを持ったjQueryオブジェクト