--- title: 反復処理プロトコル slug: Web/JavaScript/Reference/Iteration_protocols tags: - ECMAScript 2015 - Intermediate - Iterable - Iterator - JavaScript - ガイド - プロトコル - 中級者 - 反復処理 - 反復子 translation_of: Web/JavaScript/Reference/Iteration_protocols ---
{{jsSidebar("More")}}

ECMAScript 2015 で追加されたいくつかの機能の中で、反復処理プロトコルは新しい組み込みオブジェクトや構文ではなくプロトコルです。これらのプロトコルは以下のような単純な約束事によって、すべてのオブジェクトで実装することができます。

プロトコルは2つあります。 反復可能プロトコル反復子プロトコルです。

反復可能 (iterable) プロトコル

反復可能プロトコルによって、 JavaScript のオブジェクトは反復動作を定義またはカスタマイズすることができます。例えば、 {{jsxref("Statements/for...of", "for...of")}} 構造の中でどの値がループに使われるかです。一部の組み込み型は既定の反復動作を持つ組み込み反復可能オブジェクトで、これには {{jsxref("Array")}} や {{jsxref("Map")}} がありますが、他の型 ({{jsxref("Object")}} など) はそうではありません。

反復可能であるために、オブジェクトは @@iterator メソッドを実装する必要があります。これはつまり、オブジェクト (または、プロトタイプチェーン上のオブジェクトの一つ) が {{jsxref("Symbol.iterator")}} 定数にて利用できる @@iterator キーのプロパティを持つ必要があります。

プロパティ
[Symbol.iterator] 反復子プロトコルに準拠するオブジェクトを返す、引数なしの関数。

({{jsxref("Statements/for...of", "for...of")}} ループの始まりのように) オブジェクトが反復される必要があるときはいつでも、その @@iterator メソッドが引数なしで呼ばれます。そして、返される反復子は、反復される値を取得するために使用されます。

なお、この引数なしの関数が呼び出されると、反復可能オブジェクト上のメソッドとして呼び出されます。従って関数の中では、 this キーワードを反復可能オブジェクトのプロパティにアクセスするために使用して、反復の間に何を提供するかを決めることができます。

この関数は普通の関数、またはジェネレーター関数にすることができ、そのため呼び出されると、反復子オブジェクトが返されます。このジェネレーター関数の中では yield を使用してそれぞれの項目を提供することができます。

反復子 (iterator) プロトコル

反復子プロトコルは、値のシーケンス (有限でも無限でも) を生成するための標準的な方法と、すべての値が生成された場合の返値を定義します。

以下の意味で next() メソッドを実装していれば、オブジェクトは反復子になります。

プロパティ
next()

引数なしの関数で、少なくとも以下の二つのプロパティを持つオブジェクトを返します。

done (boolean)

反復子がシーケンス内の次の値を生成できるとき false の値になります。 (これは done プロパティが指定されていない場合も同じです。)

反復子が反復シーケンスを終了した場合、 true の値になります。この場合、 value は任意で反復子の返値を指定します。

value
反復子によって返される任意の JavaScript 値。 donetrue のときは省略することができます。

next() メソッドは常に donevalue などの適切なプロパティを持つオブジェクトを返します。オブジェクトでない値が返された場合 (例えば falseundefined)、 {{jsxref("TypeError")}} ("iterator.next() returned a non-object value") が発生します。

メモ: 特定のオブジェクトが反復子プロトコルを実装しているかどうかを反射的に知ることはできません。しかし、反復子プロトコルと反復可能プロトコルの両方を満たすオブジェクトを作成するのは簡単です (以下の例にあるように)。

そうすることで、反復可能オブジェクトを期待するさまざまな構文で反復子を使用できます。したがって、反復子プロトコルを実装するには反復可能プロトコルも実装しないと、ほとんど役に立ちません。

// 反復子と反復可能の両プロトコルを満たす
let myIterator = {
    next: function() {
        // ...
    },
    [Symbol.iterator]: function() { return this; }
};

例: 反復処理プロトコルの使用

{{jsxref("String")}} は組み込み反復可能オブジェクトの一例です。

let someString = 'hi';
console.log(typeof someString[Symbol.iterator]); // "function"

String の{{jsxref("String/@@iterator", "既定の反復子", "", 1)}}は文字列のコードポイントを1つずつ返します。

let iterator = someString[Symbol.iterator]();
console.log(iterator + ''); // "[object String Iterator]"

console.log(iterator.next()); // { value: "h", done: false }
console.log(iterator.next()); // { value: "i", done: false }
console.log(iterator.next()); // { value: undefined, done: true }

一部の組み込みコンストラクター — 例えば{{jsxref("Operators/Spread_operator", "スプレッド構文", "", 1)}} — は、まったく同じ反復処理プロトコルを使用しています。

console.log([...someString]); // ["h", "i"]

自身の @@iterator を提供することによって反復動作を再定義できます。:

// need to construct a String object explicitly to avoid auto-boxing
let someString = new String('hi');

someString[Symbol.iterator] = function () {
  return {
    // this is the iterator object, returning a single element (the string "bye")
    next: function () {
      return this._first ? {
        value: 'bye',
        done: (this._first = false)
      } : {
        done: true
      }
    },
    _first: true
  };
};

@@iterator を再定義することによって、反復処理プロトコルを使用する組み込みコンストラクターの動作にどれほど影響を与えるか注意してください。

console.log([...someString]); // ["bye"]
console.log(someString + ''); // "hi"

反復可能プロトコルの例

組み込み反復可能オブジェクト

{{jsxref("String")}}, {{jsxref("Array")}}, {{jsxref("TypedArray")}}, {{jsxref("Map")}}, {{jsxref("Set")}} は、すべての組み込み反復可能オブジェクトです。というのも、それらすべてのプロトタイプオブジェクトは @@iterator メソッドをもつからです。

ユーザー定義の反復可能オブジェクト

下記のように反復可能オブジェクトを生成できます。

let myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
console.log([...myIterable]); // [1, 2, 3]

反復可能オブジェクトを受け入れる組み込み API

反復可能オブジェクトを受け入れる API はたくさんあります。以下はその例です。

new Map([[1, 'a'], [2, 'b'], [3, 'c']]).get(2); // "b"

let myObj = {};

new WeakMap([
    [{}, 'a'],
    [myObj, 'b'],
    [{}, 'c']
]).get(myObj);             // "b"

new Set([1, 2, 3]).has(3); // true
new Set('123').has('2');   // true

new WeakSet(function* () {
    yield {}
    yield myObj
    yield {}
}()).has(myObj);           // true

関連情報

反復可能オブジェクトを期待する構文

いくつかの文や式は反復可能オブジェクトを期待します。例えば、 {{jsxref("Statements/for...of", "for...of")}} ループ、{{jsxref("Operators/Spread_syntax", "スプレッド演算子", "", "1")}}、{{jsxref("Operators/yield*", "yield*")}}、{{jsxref("Operators/Destructuring_assignment", "分割代入", "", "1")}} などです。

for (let value of ['a', 'b', 'c']) {
    console.log(value);
}
// "a"
// "b"
// "c"

console.log([...'abc']);   // ["a", "b", "c"]

function* gen() {
  yield* ['a', 'b', 'c'];
}

console.log(gen().next()); // { value: "a", done: false }

[a, b, c] = new Set(['a', 'b', 'c']);
console.log(a);            // "a"

非整形反復可能オブジェクト

反復可能オブジェクトの @@iterator メソッドが反復子オブジェクトを返さない場合、それは非整形反復可能オブジェクトと見なされます。

これを使用すると、ランタイムエラーやバグの挙動をもたらす可能性があります。

let nonWellFormedIterable = {}
nonWellFormedIterable[Symbol.iterator] = () => 1
[...nonWellFormedIterable] // TypeError: [] is not a function

反復子の例

簡単な反復子

function makeIterator(array) {
  let nextIndex = 0
  return {
    next: function() {
      return nextIndex < array.length ? {
        value: array[nextIndex++],
        done: false
      } : {
        done: true
      };
    }
  };
}

let it = makeIterator(['yo', 'ya']);

console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done);  // true

無限の反復子

function idMaker() {
  let index = 0;
  return {
    next: function() {
      return {
        value: index++,
        done: false
      };
    }
  };
}

let it = idMaker();

console.log(it.next().value); // '0'
console.log(it.next().value); // '1'
console.log(it.next().value); // '2'
// ...

ジェネレーターで

function* makeSimpleGenerator(array) {
  let nextIndex = 0;
  while (nextIndex < array.length) {
    yield array[nextIndex++];
  }
}

let gen = makeSimpleGenerator(['yo', 'ya']);

console.log(gen.next().value); // 'yo'
console.log(gen.next().value); // 'ya'
console.log(gen.next().done);  // true

function* idMaker() {
  let index = 0;
  while (true) {
    yield index++;
  }
}

let gen = idMaker()

console.log(gen.next().value); // '0'
console.log(gen.next().value); // '1'
console.log(gen.next().value); // '2'
// ...

ES2015 クラスで

class SimpleClass {
  constructor(data) {
    this.data = data;
  }

  [Symbol.iterator]() {
    // Use a new index for each iterator. This makes multiple
    // iterations over the iterable safe for non-trivial cases,
    // such as use of break or nested looping over the same iterable.
    let index = 0;

    return {
      next: () => {
        if (index < this.data.length) {
          return {value: this.data[index++], done: false}
        } else {
          return {done: true}
        }
      }
    }
  }
}

const simple = new SimpleClass([1,2,3,4,5]);

for (const val of simple) {
  console.log(val); // '1' '2' '3' '4' '5'
}

ジェネレーターは反復子か反復可能か

{{jsxref("Generator", "ジェネレーターオブジェクト", "", 1)}} は、反復子でも反復可能でもあります。

let aGeneratorObject = function* () {
  yield 1;
  yield 2;
  yield 3;
}();

console.log(typeof aGeneratorObject.next);
// "function", because it has a next method, so it's an iterator

console.log(typeof aGeneratorObject[Symbol.iterator]);
// "function", because it has an @@iterator method, so it's an iterable

console.log(aGeneratorObject[Symbol.iterator]() === aGeneratorObject);
// true, because its @@iterator method returns itself (an iterator), so it's an well-formed iterable

console.log([...aGeneratorObject]);
// [1, 2, 3]

仕様書

仕様書
{{SpecName('ESDraft', '#sec-iteration', 'Iteration')}}

関連情報