メインコンテンツへ移動

JavaScript ジェネレーター

無料2015-11-01#JS#JavaScript的yield#js的迭代器#JavaScript的迭代器#js的生成器#JavaScript generator

ES6 は(イテレーターの)ジェネレーターを提供します。本稿ではその構文と基本的な使い方を詳しく解説します

一.構文

作成構文:function* + yield

使用構文:next(returnValue).value/done

P.S.イテレータージェネレーターfunction* で定義されるものはイテレーターのジェネレーター(略してジェネレーター)と呼ばれます。これを呼び出すとイテレーターが返されるためです

二.イテレーターの役割

1.関数のスロットリング

時間のかかる複雑なタスクを yield で分割して少しずつ実行します。関数のカリー化 currying とも呼ばれます。詳細は JS 学習ノート 11_ 高級テクニック を参照してください

2.無限シーケンスの生成

例えばフィボナッチ数列など

3.トラバースの容易化

内部状態を手動で管理する必要がありません

三.JavaScript ジェネレーターの例

1.基本的な使い方

function* fun(a) {
    yield a + 1;
    yield a * 2;
    yield a * 2 + 1;
}

var iter = fun(3);
iter.next();
// => Object {value: 4, done: false}
iter.next();
// => Object {value: 6, done: false}
iter.next();
// => Object {value: 7, done: false}


function* fun(a) {
    yield a=a + 1;
    yield a=a * 2;
    yield a=a * 2 + 1;
}

var iter = fun(3);
// => iter.next();
Object {value: 4, done: false}
iter.next();
// => Object {value: 8, done: false}
iter.next();
// => Object {value: 17, done: false}
iter.next();
// => Object {value: undefined, done: true}

関数の実行が yield に遭遇すると、まず yield の後ろの値を return し、関数実行の context を保存します(ブレークポイントを保存するのと似た効果)。次に next() を呼び出すと、context を復元し、yield の次の文から実行を開始し、yield または return に遭遇すると終了します

2.高度な使い方

function* fib() {
    var a = 1;
    var b = 1;

    while (true) {
        var current = b;
        b = a;
        a = a + current;

        var reset = yield current;
        if (reset) {
            a = 1;
            b = 1;
        }
    }
}

var fibSeq = fib();
fibSeq.next();
// => Object {value: 1, done: false}
fibSeq.next();
// => Object {value: 1, done: false}
fibSeq.next();
// => Object {value: 2, done: false}
fibSeq.next();
// => Object {value: 3, done: false}
fibSeq.next();
// => Object {value: 5, done: false}
fibSeq.next(false);
// => Object {value: 8, done: false}
fibSeq.next(true);
// => Object {value: 1, done: false}
fibSeq.next(false);
// => Object {value: 1, done: false}
fibSeq.next(false);
// => Object {value: 2, done: false}

next() はパラメータを受け取ることができ、このパラメータは yield の戻り値として関数に返されます。これにより関数内部の状態を制御できます。例えば上記の reset の有無など

3.少し複雑な例

function* fun(a) {
    a = yield a + 1;
    a = yield a * 2;
    yield a * 2 + 1;
}

var iter = fun(3);
iter.next(0);
// => Object {value: 4, done: false}
iter.next(0);
// => Object {value: 0, done: false}
iter.next(1);
// => Object {value: 3, done: false}

同様のもの:

function* fun(a) {
    a = yield a = a + 1;
    a = yield a = a * 2;
    yield a = a * 2 + 1;
}
var iter = fun(3);
iter.next(0);
// => Object {value: 4, done: false}
iter.next(0);
// => Object {value: 0, done: false}
iter.next(1);
// => Object {value: 3, done: false}

内部実行メカニズムは同じで、書き方が少し複雑なだけです

4.その他

さらに、イテレーターには throw()return() メソッドがあります。FF は両方実装していますが、Chrome は前者のみ実装しています。詳細は MDN Iterators and generators を参照してください

また、1 つのイテレーターから別のイテレーターを生成する構文もあります。例えば:

function* fun() {
    yield 1;
    yield 2;
}
var iter = fun();
var newIter = (for (i of iter) i * 2);
newIter.next();
// => Object { value: 2, done: false }
newIter.next();
// => Object { value: 4, done: false }

注意:MDN の例は誤っています。必ず for...of であり、丸括弧でなければなりません(例中の for...in と角括弧は誤りです)

FF はこの構文をサポートしていますが、Chrome はサポートしていません

三.関連構文

1.for...of、for...in、forEach

for...of はプロパティ値をトラバースするために使用され、for...in はプロパティ名をトラバースするために使用されます。forEach は Array.prototype 上のメソッドで、プロパティ名とプロパティ値の両方をトラバースできます

さらに、for...of は DOM NodeList とカスタムイテレーター(function* + yield)もトラバースでき、ES6 の新機能です

例は以下の通り:

var arr = [3, 5, 7];
arr.foo = "hello";

for (var i in arr) {
   console.log(i); // logs "0", "1", "2", "foo"
}
for (var i of arr) {
   console.log(i); // logs "3", "5", "7"
}


let arr = [3, 5, 7];
arr.foo = "hello";

arr.forEach(function (element, index) {
    console.log(element); // logs "3", "5", "7"
    console.log(index);   // logs "0", "1", "2"
});

2.yield*

yield と機能は似ており、トラバースに使用されますが、yield* の後ろにはイテレーターオブジェクトが続き、後ろのイテレーターに入ってトラバースを完了してから戻るという役割です。例えば:

function* g1() {
  yield 2;
  yield 3;
  yield 4;
}

function* g2() {
  yield 1;
  yield* g1();
  yield 5;
}

var iterator = g2();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

yield* により、イテレーターをネストできるようになり、階層構造を「線形」にトラバースできます(もちろん、すべての下層構造のイテレーターオブジェクトを取得できることが前提ですが)

参考資料

コメント

コメントはまだありません

コメントを書く