MENU

Symbol.iterator 徹底解説:JavaScript の反復処理を支える仕組み

目次

1. Symbol.iterator とは?

•  定義  

Symbol.iterator は、JavaScript に最初から用意されている「特別な印(シンボル)」のひとつです。

•  役割  

そのオブジェクトが「順番に中身を取り出せるもの(反復可能=iterable)」だと教えるための合図になります。  

→ これがあると for…of や …(スプレッド構文)で中身を順番に取り出せる。

•  戻り値  

Symbol.iterator に対応するメソッドは「イテレーター」と呼ばれる専用の仕組みを返します。  

イティセル/コード専門官

つまり、Symbol.iterator を実装すれば、そのオブジェクトは for…of やスプレッド構文で展開できるようになります。

イティセル/コード専門官

要するに、順番に値を取り出せるようにして、次はこれだよ、ここで終わりだよ、と教えてくれる仕組みです。

イテレーターとは、オブジェクトの要素を「順番に1つずつ取り出すための仕組み」を持ったオブジェクトのことです。

2. 代表的な反復可能オブジェクト

イティセル/コード専門官

Symbol.iterator に対応している代表的なオブジェクトは、次のように反復処理が可能です。つまり、これらは「順番に中身を取り出せる仲間たち」です。

•  Array

•  String

•  Map

•  Set

•  TypedArray

•  arguments オブジェクト

•  DOM コレクション(NodeList など)

イティセル/コード専門官

例えば、arr と Symbol.iterator を組み合わせて typeof を実行すると “function” が返ってきます。これは「関数」だからです。
 
一方、arr だけで typeof を実行すると “object” になります。配列はオブジェクトの一種だからです。  

つまり、Symbol.iterator を使わなければただの配列ですが、Symbol.iterator を参照すると「中身を順番に取り出す工場(関数)」が現れるイメージになります。

const arr = [1, 2, 3];

console.log(typeof arr[Symbol.iterator]); 
リクナ / JavaScript統括官

“function”

実行結果
"function"
イティセル/コード専門官

arr の中身は [1, 2, 3] です。  
arr[Symbol.iterator]() を呼ぶと「配列を順番に取り出す仕組み(イテレーター)」が作られます。

console.log(iterator.next()); の結果は { value: 1, done: false } になります。
•  value: 1 → 配列の最初の要素が返る
•  done: false → まだ次の要素が残っている

要するに、これは「最初の呼び出しだから 1 が返ってきた。でも配列にはまだ続きがあるよ」というサインです。

わかりやすくいうと
•  value: 1 → 「 arr=[1, 2, 3] の内今取り出した値は 1 です」
•  done: false → 「まだ次の値(2, 3)が残っています」

const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next());
リクナ / JavaScript統括官

{ value: 1, done: false }

実行結果
{ value: 1, done: false }

3. イテレーターの仕組み

イティセル/コード専門官

イテレーターは next() メソッドを持つオブジェクトで、呼び出すたびに { value, done } を返します。  
簡単に言うと、next() を呼ぶごとに「次の値」と「まだ続きがあるかどうか」を教えてくれる仕組みです。

•  console.log(iterator.next()); → ラウンド1  
最初の 10 が出てきて「まだ続きあるよ!」

•  console.log(iterator.next()); → ラウンド2  
次は 20 が出てきて「まだ続きあるよ!」

•  console.log(iterator.next()); → ラウンド3  
次は 30 が出てきて「まだ続きあるよ!」

•  console.log(iterator.next()); → ラウンド4  
あれれ、もう値がないので「終わりだよ!」

const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
リクナ / JavaScript統括官

{ value: 10, done: false }
{ value: 20, done: false }
{ value: 30, done: false }
{ value: undefined, done: true }

実行結果
{ value: 10, done: false }
{ value: 20, done: false }
{ value: 30, done: false }
{ value: undefined, done: true }

4. カスタムオブジェクトに Symbol.iterator を実装する

イティセル/コード専門官

これは「自作オブジェクトを for…of で回せるようにする仕組み」のデモです。Symbol.iterator を実装すると、オブジェクトを配列のように順番に取り出せるようになります。

const range = {
  start: 1,
  end: 5,
  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;
    return {
      next() {
        if (current <= end) {
          return { value: current++, done: false };
        }
        return { done: true };
      }
    };
  }
};

for (const num of range) {
  console.log(num);
}
イティセル/コード専門官

まず、さっきのコードを分解して説明します。

イティセル/コード専門官

range は start と end というプロパティを持つ普通のオブジェクトです。 
start は 1 から始まることを、end は 5 までで終わることを表します。

このままでは for…of で回すことはできません。  

const range = {
  start: 1,
  end: 5,
  ...
};
イティセル/コード専門官

そこで Symbol.iterator という特別なキーを実装すると、「このオブジェクトは反復可能(iterable)」と認識され、for…of で扱えるようになります。

[Symbol.iterator]() {
  let current = this.start;
  const end = this.end;
  return {
    next() {
      if (current <= end) {
        return { value: current++, done: false };
      }
      return { done: true };
    }
  };
}
イティセル/コード専門官

Symbol.iterator が返すのは イテレーターオブジェクト です。  
このオブジェクトには next() メソッドがあり、呼び出すたびに { value, done } を返します。
•  value → 今の値
•  done → まだ続きがあるかどうか

return {
  next() {
    if (current <= end) {
      return { value: current++, done: false };
    }
    return { done: true };
  }
};
イティセル/コード専門官

for…of 構文は内部的に range[Symbol.iterator]() を呼び出し、done: true が返るまで next() を繰り返します。

for (const num of range) {
  console.log(num);
}
イティセル/コード専門官

1.  range[Symbol.iterator]() が呼ばれる  
→ current = 1, end = 5 のイテレーターが返る
2.  next() 1回目 → { value: 1, done: false }
3.  next() 2回目 → { value: 2, done: false }
4.  …
5.  next() 5回目 → { value: 5, done: false }
6.  next() 6回目 → { done: true } → ループ終了

リクナ / JavaScript統括官

1, 2, 3, 4, 5

実行結果
1, 2, 3, 4, 5
イティセル/コード専門官

要するに、Symbol.iterator がなければ for…of は使えません。ちょうど、歯車がないと時計が動かないようなものです。

5. 注意点・落とし穴

for…in と for…of の混同

イティセル/コード専門官

for…in と for…of は見た目が似ていますが、返すものが異なります。

•  for…in → オブジェクトの「キー(プロパティ名)」を列挙する構文。配列に使うとインデックスが返ります。
•  for…of → iterable の「値」を列挙する構文。配列に使うと要素そのものが返ります。

イティセル/コード専門官

配列 [“a”,”b”,”c”] に対して for…in を使うと、要素そのものではなく インデックス(0,1,2) が返ります。

const arr = ["a", "b", "c"];

for (const key in arr) {
  console.log(key);
}
リクナ / JavaScript統括官

0, 1, 2

実行結果
0, 1, 2
イティセル/コード専門官

for…of は値をそのまま返してくれます。  
配列 [“a”,”b”,”c”] なら、順番に “a” → “b” → “c” が取り出されます。

const arr = ["a", "b", "c"];

for (const value of arr) {
  console.log(value);
}
リクナ / JavaScript統括官

a, b, c

実行結果
a, b, c
イティセル/コード専門官

つまり、for…inとfor…ofは似てるので間違いやすい。そこは気をつけてください。

オブジェクトリテラルはデフォルトで iterable ではない:必要なら自分で Symbol.iterator を実装する。

なぜオブジェクトは iterable じゃないのか

•  配列や文字列は「順番に値を取り出す」仕組みがあるため、for…of で回せる。

•  一方、オブジェクトリテラル { a: 1, b: 2 } は「順序付きの値の列」というより「キーと値のマップ」なので、デフォルトでは iterable ではない。

イティセル/コード専門官

わかりやすくいうと、オブジェクトリテラル {} はiterableに対応していないからです。

そのため以下のように書くとエラーになります。

const obj = { a: 1, b: 2 };

for (const v of obj) {
  console.log(v);
}
リクナ / JavaScript統括官

…?
for…of 構文だと?しかも、オブジェクトリテラル {} だって?そんなの話は聞いてないよ…

TypeError: obj is not iterable
(そのオブジェクトは iterable(反復可能) ではないので、for…of やスプレッド構文などで使えません)

実行結果
TypeError: obj is not iterable


自分で Symbol.iterator を実装する

イティセル/コード専門官

{} はそのままでは iterable じゃないので、対応させたいなら Symbol.iterator を実装してあげる必要があります。

const obj = {
  a: 1,
  b: 2,
  *[Symbol.iterator]() {
    for (const key of Object.keys(this)) {
      yield [key, this[key]];
    }
  }
};

for (const [k, v] of obj) {
  console.log(k, v);
}
リクナ / JavaScript統括官


あ、Symbol.iteratorがあるから反復だったのね
a 1
b 2

実行結果
a 1
b 2

6. まとめ

JavaScript の反復処理を支えているのは、目に見えない小さな歯車 Symbol.iterator です。  

配列や文字列が当たり前のように for…of で回せるのも、裏でこの歯車が動いているから。

一方で {} はその歯車を持っていない。だから「回そうとすると止まってしまう」。  

でも、自分で Symbol.iterator を実装すれば、オブジェクトだって立派にレーンに乗って流れ始める。

要するに、

•  なぜ配列は回せるのか

•  なぜオブジェクトは回せないのか

•  どうすれば回せるようになるのか

その答えはすべて Symbol.iterator にある。

これを理解すれば、JavaScript の「反復処理」という舞台裏が一気にクリアになります。  

「ただの構文」だと思っていた for…of の奥に、こんな仕組みが隠れていたんだ――  

そう気づいた瞬間、コードの見え方が変わります。

もしこの記事が役に立ったと思ったら、シェアやコメントで教えてください。  

いただいた声を今後の改善に活かしていきます。  

最後まで読んでくださり、本当にありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ITTIのアバター ITTI 運営長

私はフロントエンドエンジニアを目指す初心者で、ITパスポートを取得済みです。現在はCopilotを活用しながらAIや最新のIT技術を学び、日本の開発現場で求められるチーム開発やセキュリティの知識を吸収しています。学んだことはコードや仕組みを整理し、わかりやすく発信することで、同じ学びの途中にいる人たちの力になりたいと考えています。

コメント

コメントする

目次