反復可能とイテレータ — for...of で回せるものの正体

for...of とスプレッドが使えるかどうかは Symbol.iterator を持つかで決まります。自分で作る方法、ジェネレータ、1度きりしか回せないこと、無限の列を扱う形まで実行して確かめます。

実行して検証済み処理系Node 22.22.3対応ES2015難易度●●●○○目安10更新

for...of とスプレッドが使えるかどうかは、たった1つの決まりで決まります。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
function function function function undefined
javascript
for (const value of { a: 1 }) {
console.log(value);
}
TypeError: {(intermediate value)} is not iterable

Symbol.iterator という名前の関数を持っていれば回せます。 素のオブジェクトはこれを持っていません。だから落ちます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
a 1 b 2
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
{ value: 10, done: false } { value: 20, done: false } { value: undefined, done: true }
回せるもの 回せないもの
配列 / 文字列 / Set / Map 素のオブジェクト
arguments { length: 3 } のような形だけ似たもの
ジェネレータ 数値・真偽値

素のオブジェクトを回すなら、いったん配列にします。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ 1, 2, 3 ] [ 1, 2, 3 ]
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
{ value: 1, done: false } { value: 2, done: false } { value: 99, done: true } { value: undefined, done: true } [ 1, 2 ]

Object.keys() / values() / entries()

中身は「次を返す関数」だけ

Symbol.iterator を呼ぶとイテレータが返り、それに next() を聞きます。

javascript
function* gen() {
yield 1;
yield 2;
}

const g = gen();

console.log([...g]);
console.log([...g]);
[ 1, 2 ] []
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ 1, 2 ] [ 1, 2 ]

donetrue になるまで呼び続ける。 for...of がやっているのはこれだけです。

自分で作る

Symbol.iterator を持たせれば、素のオブジェクトでも回せます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ 1, 2 ] [ 1, 2 ]
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ 1, 2, 3, 4, 5 ]

function*yield で書くと、next() を自分で用意しなくて済みます。 これをジェネレータと呼びます。

ジェネレータ

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
1 2 後始末
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
1 [ 2 ] 2 Set(2) { 1, 2 } { a: 1 }
  • yield止まって値を返し、次に呼ばれたら続きから動く
  • return した値は done: true と一緒に来る
  • for...of とスプレッドは return の値を拾いません[1, 2] だけ)

return に意味のある値を入れないでください。 多くの書き方で消えます。

1度回すと空になる

ここがいちばん事故になります。

配列は何度でも回せますが、イテレータは使い切りです。

2回使うなら、先に配列にしてください。

終わらない列も書ける

return するまで作られないので、無限に続く列を書いても止まりません。

必要になったときにだけ計算されます。これを遅延評価と呼びます。

[...naturals()] と書くと止まりません。必ず打ち切りを挟んでください。

途中で抜けたときの後始末

break で抜けても、finally は動きます。 ファイルや接続を閉じる処理を、安心して書けます。 → Error と try / catch / finally / throw

どこで効いてくるか

Symbol.iterator を持っていれば、言語のいろいろな場所がそのまま使えます。

for...of・スプレッド・分割代入・Array.from()new Set()new Map()入口が1つなので、対応する場所が一気に増えます。

まとめ

  • 回せるかどうかはSymbol.iterator を持つかだけで決まる
  • 素のオブジェクトは持っていない。Object.entries() を挟む
  • 中身は next(){ value, done } を返すだけ
  • function*yield で短く書ける
  • イテレータは使い切り。 2回使うなら先に配列にする
  • 終わらない列も書ける。ただし打ち切りを忘れると止まらない
  • break で抜けても finally は動く

この記事の根拠

  1. Iteration — ECMAScript® 2026 Language Specificationtc39.es
  2. Generator Function Definitions — ECMAScript® 2026 Language Specificationtc39.es
  3. 反復処理プロトコル — MDNdeveloper.mozilla.org

掲載しているコード例は、公開前に読み手が押したときに動くのと同じもので実行して出力を突き合わせています。結果はリポジトリに残しています。

このページの原文(Markdown)