概要: このチュートリアルでは、JavaScriptジェネレーターとその効果的な使い方について学びます。
JavaScriptジェネレーター入門
JavaScriptでは、通常の関数は、実行完了モデルに基づいて実行されます。途中で一時停止したり、一時停止した場所から再開したりすることはできません。例えば、
function foo() {
console.log('I');
console.log('cannot');
console.log('pause');
}
Code language: JavaScript (javascript)
foo()
関数は上から下へ実行されます。foo()
関数を終了する唯一の方法は、関数から戻るか、エラーをスローすることです。foo()
関数を再度呼び出すと、実行は上から下へと開始されます。
foo();
出力
I
cannot
pause
ES6では、通常の関数とは異なる新しい種類の関数、関数ジェネレーターまたはジェネレーターが導入されています。
ジェネレーターは途中で一時停止し、一時停止した場所から再開できます。例えば、
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
ループで使用できます。