概要: このチュートリアルでは、JavaScriptジェネレーターとその効果的な使い方について学びます。
JavaScriptジェネレーター入門
JavaScriptでは、通常の関数は、実行完了モデルに基づいて実行されます。途中で一時停止したり、一時停止した場所から再開したりすることはできません。例えば、
function foo() {
console.log('I');
console.log('cannot');
console.log('pause');
}Code language: JavaScript (javascript)foo()関数は上から下へ実行されます。foo()関数を終了する唯一の方法は、関数から戻るか、エラーをスローすることです。foo()関数を再度呼び出すと、実行は上から下へと開始されます。
foo();出力
I
cannot
pauseES6では、通常の関数とは異なる新しい種類の関数、関数ジェネレーターまたはジェネレーターが導入されています。
ジェネレーターは途中で一時停止し、一時停止した場所から再開できます。例えば、
function* generate() {
console.log('invoked 1st time');
yield 1;
console.log('invoked 2nd time');
yield 2;
}Code language: JavaScript (javascript)generate()関数を詳しく見てみましょう。
- まず、
functionキーワードの後にアスタリスク(*)があります。アスタリスクは、generate()が通常の関数ではなく、ジェネレーターであることを示します。 - 次に、
yieldステートメントは値を返し、関数の実行を一時停止します。
次のコードは、generate()ジェネレーターを呼び出します。
let gen = generate();Code language: JavaScript (javascript)generate()ジェネレーターを呼び出すと、
- まず、コンソールには何も表示されません。
generate()が通常の関数であれば、何らかのメッセージが表示されるはずです。 - 次に、
generate()から返り値として何かが返されます。
返り値をコンソールに表示してみましょう。
console.log(gen);Code language: JavaScript (javascript)出力
Object [Generator] {}Code language: CSS (css)したがって、ジェネレーターは呼び出されると、その本体を実行せずにGeneratorオブジェクトを返します。
Generatorオブジェクトは、doneとvalueの2つのプロパティを持つ別のオブジェクトを返します。言い換えれば、Generatorオブジェクトは反復可能です。
以下は、Generatorオブジェクトに対してnext()メソッドを呼び出しています。
let result = gen.next();
console.log(result);Code language: JavaScript (javascript)出力
invoked 1st time
{ value: 1, done: false }
Code language: CSS (css)ご覧のとおり、Generatorオブジェクトは、1行目でメッセージ'invoked 1st time'を出力し、2行目で値1を返す(yield)ことで、その本体を実行します。
yieldステートメントは1を返し、2行目でジェネレーターを一時停止します。
同様に、次のコードは、Generatorのnext()メソッドを2回目に呼び出します。
result = gen.next();
console.log(result);
Code language: JavaScript (javascript)出力
invoked 2nd time
{ value: 2, done: false }Code language: CSS (css)今回は、Generatorはメッセージ'invoked 2nd time'を出力する3行目から実行を再開し、2を返します(またはyieldします)。
以下は、ジェネレーターオブジェクトのnext()メソッドを3回目に呼び出します。
result = gen.next();
console.log(result);Code language: JavaScript (javascript)出力
{ value: undefined, done: true }Code language: CSS (css)ジェネレーターは反復可能であるため、for...ofループを使用できます。
for (const g of gen) {
console.log(g);
}Code language: JavaScript (javascript)以下は出力です。
invoked 1st time
1
invoked 2nd time
2
JavaScriptジェネレーターのその他の例
次の例は、ジェネレーターを使用して無限のシーケンスを生成する方法を示しています。
function* forever() {
let index = 0;
while (true) {
yield index++;
}
}
let f = forever();
console.log(f.next()); // 0
console.log(f.next()); // 1
console.log(f.next()); // 2
Code language: JavaScript (javascript)foreverジェネレーターのnext()メソッドを呼び出すたびに、0から始まるシーケンスの次の数が返されます。
ジェネレーターを使用してイテレーターを実装する
イテレーターを実装する場合、next()メソッドを手動で定義する必要があります。next()メソッドでは、現在の要素の状態も手動で保存する必要があります。
ジェネレーターは反復可能であるため、イテレーターを実装するためのコードを簡略化できます。
以下は、イテレーターチュートリアルで作成されたSequenceイテレーターです。
class Sequence {
constructor( start = 0, end = Infinity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex < this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
}
}
}
}
Code language: JavaScript (javascript)そして、ジェネレーターを使用する新しいSequenceイテレーターがこちらです。
class Sequence {
constructor( start = 0, end = Infinity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
* [Symbol.iterator]() {
for( let index = this.start; index <= this.end; index += this.interval ) {
yield index;
}
}
}Code language: JavaScript (javascript)ご覧のとおり、Symbol.iteratorメソッドは、ジェネレーターを使用することで非常にシンプルになっています。
次のスクリプトは、Sequenceイテレーターを使用して、1から10までの奇数のシーケンスを生成します。
let oddNumbers = new Sequence(1, 10, 2);
for (const num of oddNumbers) {
console.log(num);
}Code language: JavaScript (javascript)出力
1
3
5
7
9ジェネレーターを使用してBagデータ構造を実装する
Bagは、要素を収集し、要素を反復処理できるデータ構造です。アイテムの削除はサポートしていません。
次のスクリプトは、Bagデータ構造を実装しています。
class Bag {
constructor() {
this.elements = [];
}
isEmpty() {
return this.elements.length === 0;
}
add(element) {
this.elements.push(element);
}
* [Symbol.iterator]() {
for (let element of this.elements) {
yield element;
}
}
}
let bag = new Bag();
bag.add(1);
bag.add(2);
bag.add(3);
for (let e of bag) {
console.log(e);
}Code language: JavaScript (javascript)出力
1
2
3まとめ
- ジェネレーターは、ジェネレーター関数
function* f(){}によって作成されます。 - ジェネレーターは、呼び出されたときにすぐにはその本体を実行しません。
- ジェネレーターは途中で一時停止し、一時停止した場所から実行を再開できます。
yieldステートメントは、ジェネレーターの実行を一時停止し、値を返します。 - ジェネレーターは反復可能であるため、
for...ofループで使用できます。