イテレーターとジェネレーター

この記事は編集レビューを必要としています。ぜひご協力ください

コレクションの中の各項目を処理するのは、とても一般的な操作です。JavaScript は簡単な for ループから map()filter() にいたるまで、コレクションを反復処理する方法を取り揃えています。またイテレーターとジェネレーターによって、言語のコア部分に反復処理のコンセプトが直接的に取り入れられており、for...of ループの動作をカスタマイズできる仕組みをもたらします。

詳細についてはこちらもご覧ください :

イテレーター

イテレーターとは、一連の処理中において現在の処理位置を把握しつつ、コレクション中の項目へ一つずつアクセスする方法を備えたオブジェクトのことです。JavaScript においては、イテレーターは一連の処理中の次の項目を返す next() メソッドを提供するオブジェクトです。このメソッドは donevalue という 2 つのプロパティを持つオブジェクトを返します。

ひとたび作成すれば、イテレーターオブジェクトは明示的に next() を繰り返し呼び出して使用できます。

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

初期化を行えば、next() メソッドを呼び出すことで、オブジェクトにあるキーと値のペアへ順番にアクセスすることができます :

var it = makeIterator(['yo', 'ya']);
console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done);  // true

ジェネレーター

カスタムイテレーターは便利なツールですが、これを作るには明示的な内部状態の管理が必要となるため、注意深いプログラミングを必要とします。Generators はもう1つの手法です: つまり、自分自身で状態を管理する単一の関数を書くことで、イテレータ―を実装することができます。

ジェネレーターは、イテレーターを生成するファクトリーとして働く、特別な種類の関数です。1つ以上の yield 式を持ち、 function* 構文を使用している場合に、関数はジェネレーターとなります。.

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

var gen = idMaker();

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

反復可能

for..of のループ構造内で内部の値をループ処理できるような、反復処理の挙動が定義されているオブジェクトのことを反復可能 (iterable)(イテラブル)と呼びます。ArrayMap といったビルトイン型のいくつかは、他の型(Object など)と違ってデフォルトで反復処理できる性質を備えています。

反復可能にするには、そのオブジェクトに @@iterator メソッドを実装しなければなりません。それはつまり、そのオブジェクト(あるいはプロトタイプチェーンで継承するオブジェクトのうちの一つ)が Symbol.iterator キーをプロパティとして持っている必要があります。

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

以下のようにして反復可能オブジェクトを自作することができます :

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

for (let value of myIterable) { 
    console.log(value); 
}
// 1
// 2
// 3

or

[...myIterable]; // [1, 2, 3]

ビルトイン反復可能オブジェクト

StringArrayTypedArrayMapSet はすべてビルトイン反復可能オブジェクトです。これらオブジェクトはすべて、そのプロトタイプオブジェクトに Symbol.iterator メソッドを備えています。

反復可能オブジェクトが必要な構文

for-of ループ、展開演算子yield*分割代入 などの文や式は、反復可能オブジェクトを必要とします。

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

[...'abc']; // ["a", "b", "c"]

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

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

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

 

高度なジェネレーター

ジェネレーターは要求に応じて yield 文により生成される値を計算しており、多くの計算が必要な一連のデータを効率的に表現したり、前出のとおり無限のシーケンスを表現したりすることを可能にします。

またジェネレータの内部状態を変更するのに next() メソッドを使用できます。next() に渡された値はジェネレーターが中断される際の直前の yield 式の結果として扱われます。

以下のフィボナッチ数列ジェネレーターでは数列を再起動するのに next(x) を使っています :

function* fibonacci() {
  var fn1 = 0;
  var fn2 = 1;
  while (true) {  
    var current = fn1;
    fn1 = fn2;
    fn2 = current + fn1;
    var reset = yield current;
    if (reset) {
        fn1 = 0;
        fn2 = 1;
    }
  }
}

var sequence = fibonacci();
console.log(sequence.next().value);     // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2
console.log(sequence.next().value);     // 3
console.log(sequence.next().value);     // 5
console.log(sequence.next().value);     // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2

ジェネレーターの throw() メソッドを呼び出して発生させたい例外値を渡すことで、ジェネレーターに例外を発生させることができます。これにより、まるで停止中の yieldthrow value 文に替わったかのように、ジェネレーターが停止した際の状況に応じて例外が発生します。

発生した例外の処理中に yield に出くわさない場合は、throw() を通してその例外が呼び出し元へと伝播し、その後 next() を呼び出した結果の done プロパティは true となります。

またジェネレーターは、与えられた値を返してジェネレーター自身の処理を終了させる return(value) メソッドを持っています。

ドキュメントのタグと貢献者

タグ: 
 このページの貢献者: unarist, Uemmra3, x2357, teoli, naganumat, ethertank, yyss
 最終更新者: unarist,