JavaScript 反復処理 ジェネレータの解説
ES6で追加された、for…of文の挙動に関わるジェネレータを解説しています。ジェネレータとは、イテレータと同様の機能を持つGeneratorオブジェクトを作成するための関数です。ジェネレータを使うとイテレータと同様の反復処理をより簡潔に記述できます。
ジェネレータ関数の記法
構文:ジェネレータ関数の記法
ジェネレータ関数の宣言
function* ジェネレータ関数() {
yield 値;
return 値;
}
ジェネレータ関数の関数式
let ジェネレータ関数 = function* (){
yield 値;
return 値;
}
ジェネレータ関数の実行
let Generatorオブジェクト = ジェネレータ関数();
- function*:ジェネレータ関数を宣言するには、関数キーワード(function)の後ろにスター(*)を記述します。
- yield :ジェネレータ関数内のyield文は、イテレータのnextメソッドが呼びさだれたときに、{ done: false, value: 値 }に一致するオブジェクトを返します。
- return :ジェネレータ関数内のreturn文は、イテレータのnextメソッドが呼びだされたときに、{ done: true, value: 値 }に一致するオブジェクトを返します。
ジェネレータ関数を実行するとGeneratorオブジェクトが生成されます。生成されたGeneratorオブジェクトには、イテレータと同様にnextメソッドが格納されます。また、nextメソッドを実行した時の戻り値も、{ done, value }という形式のオブジェクトになります。通常の関数では、関数の実行は「関数内の処理をすべて終えるまで処理を継続すること」です。しかし、ジェネレータ関数の実行は、あくまで「Generatorオブジェクトの生成」を意味します。そして、作成された「Generatorオブジェクトのnextメソッドが呼び出されたタイミングで、yieldまたはreturnが見つかった地点までジェネレータ関数内の処理が実行されます」。
function* gen1to3{
let index = 1;
yield index; //1回目のnext()によってこの地点まで実行が完了
index++;
yield index; //2回目のnext()によってこの地点まで実行が完了
index++;
return index; //3回目のnext()によってこの地点まで実行が完了
}
const generator = gen1to3); //ジェネレータ関数の実行はGeneratorオブジェクトの生成を意味する
//トの生成を意味する
console.log( generator.next() ); //next()の実行によってジェネレータ関数内のコードが実行される
>{ value: 1, done: false } //1つ目のyieldによって返されるオブジェクト
console.log( generator.next() );
>{ value: 2, done: false } //2つ目のyieldによって返されるオブジェクト
console.log( generator.next() );
>{ value: 3, done: false } //returnによって返されるオブジェクト
上記のコードで、gen1to3()によって作成したgeneratorオブジェクトのnextメソッドを実行すると、yieldまたはreturnに続く値が、戻り値のオブジェクトのvalueプロパティに設定されていることがわかります。また、このときnextメソッドの実行ごとに実行されるジェネレータ関数のコードは、「次のyieldまたはreturnまで」であることが読み取れます。
このようにしてジェネレータ関数は反復処理を実行します。
1ずつインクリメントした値を返すジェネレータ
function* genIterator( max ){
let value = 0;
while( value < max ){
yield value++;
}
}
const iterator = genIterator( 3 );
console.log( iterator .next() );
>{ done: false, value: 0 }
console.log( iterator .next() );
>{ done: false, value: 1 }
console.log( iterator .next() );
>{ done: false, value: 2 }
console.log( iterator .next() );
>{ done: false, value: undefined }
上記のコードでは、while文の中でyield文を使っているため、nextメソッドの実行のたびにyield文によってValueが返されます。このように、ジェネレータを使った場合には、イテレータを直接記述する場合に比べ、処理を簡潔に記述でいます。
反復可能オブジェクトの作成
イテレータと同様に、ジェネレータ関数をオブジェクトのSymbol.iteratorプロパティに設定すれば反復可能オブジェクトを作成できます。
構文:ジェネレータを使った反復可能オブジェクトの記法
オブジェクトに直接設定する場合
const iterableObject = {
[ Symbol.iterator ]: function* () { }
};
//ES6のメソッドの省略法
const iterableObject = {
*[ Symbol.iterator ] () { }
}
コンストラクタ関数に設定する場合(Arrayの場合)
Array.prototype[ Symbol.iterator ] = function* () { }
クラスに設定する場合
class IterableClass{
*[ Symbol.iterator ]() { }
}
オブジェクトのプロパティをすべて列挙するiterableクラスを作成
class Iterable {
*[ Symbol.iterator ] () {
for( let key in this ){
yield [ key, this[ key ] ];
}
}
}
const fruits = new Iterable();
fruits.apple = "リンゴ";
fruits.banana= "バナナ";
for( const row of fruits ){
console.log( row[ 0 ], row[ 1 ] );
}
>apple リンゴ
>banana バナナ
Iterableクラスを継承して反復可能オブジェクトを作成
class Iterable {
*[ Symbol.iterator ] () {
for( let key in this ){
yield [ key, this[ key ] ];
}
}
}
class Person extends Iterable { //Iterableクラスを継承することで、反復可能オブジェクトになる
constructor( name, age, gender ){
super();
this.name = name;
this.age = age;
this.gender = gender;
}
}
const taro = new Person( "太郎", 118 , "男" );
for( const row of taro){
console.log( row[ 0 ], row[ 1 ] );
}
>name 太郎
>age 18
>gender 男
このように、反復可能オブジェクトは、簡単に作成できます。
Generatorオブジェクトとイテレータの機能面の違い
ジェネレータ関数によって生成されるGeneratorオブジェクトは、イテレータとして利用できるだけでなく、反復可能オブジェクトとしても利用できます。一方、イテレータは、オブジェクトに設定しない限り、イテレータ自身を反復可能オブジェクトとして利用することはできません。反復可能であるGeneratorオブジェクトは、for…of文で直接使うこともできます。
Generatorオブジェクトは反復可能オブジェクトでもある
function* fruits(){
yield "バナナ";
yield "リンゴ";
yield "メロン";
}
const iterableObject = fruits();
for( const fruit of iterableObject ){ //反復可能オブジェクトなため、for…of文が使用可能
console.log( fruit );
}
>バナナ
>リンゴ
>メロン