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

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

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

コレクションの各アイテムを処理するのは、とても一般的な操作です。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

反復可能

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

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

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

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

var myIterable = {}
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
[...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"

ジェネレータ

独自のイテレータは役に立つ手段ですが、内部の状態を明示的に管理する必要があることから、イテレータの作成では注意深くプログラミングすることが求められます。ジェネレータは、強力な代替手段を提供します。自身の状態を管理する関数を一つ記述することにより、反復処理アルゴリズムの定義を可能にします。

ジェネレータは、イテレータの生成元として働く特殊な関数です。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
// ...

高度なジェネレータ

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

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

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

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

var sequence = fibonacci();
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().value);     // 13
console.log(sequence.next(true).value); // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2
console.log(sequence.next().value);     // 3

註: 興味深いことに、next(undefined) の呼び出しは next() と等価です。しかしその一方、next() を呼び出す際に undefined 以外の値で新たなジェネレータを開始すると、TypeError 例外が発生します

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

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

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

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

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