JacaScriptのイテレータとジェネレータの概念について、自分自身が理解するためにもその基本をまとめます。
JavaScriptのイテレーターとイテラブル
■ イテレーターとは
シーケンス(配列やオブジェクトなど)を反復処理するためのオブジェクトであり、各要素を順番に返すためのオブジェクトを指します。イテレーターは以下の特徴を持っています。
-
プロトコルの実装
イテレーターはnext()メソッド
を持ち、このメソッドは次の値(value)と、シーケンスが終了したかどうかを示すフラグ(done)を返します。返されるオブジェクトは{value, done}
という形式です。・value: 現在の要素の値
・done: シーケンスが終了した場合はtrue、まだ要素が残っている場合はfalse
function iteratorSample(array){
let index = 0;
return {
next: function(){
if(index < array.length){
return {value: array[index++], done: false};
}else{
return {value: undefined, done: true};
}
}
}
}
const iterator = iteratorSample([1, 2, 3]);
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: undefined, done: true }
■ イテラブルとは
イテラブルとは、反復可能なオブジェクトを指し、JavaScriptでは、[Symbol.iterator]()メソッドを持つオブジェクトを意味します。iterator.jsのconst iterator
をイテラブルにするには、const iterator
を返す [Symbol.iterator] メソッドを作ると、イテラブルオブジェクトを作成できます。
function iteratorSample(array){
let index = 0;
return {
next: function(){
if(index < array.length){
return {value: array[index++], done: false};
}else{
return {value: undefined, done: true};
}
}
}
}
const iterator = iteratorSample([1, 2, 3]);
const iterable = {};
iterable[Symbol.iterator] = () => {
return iterator;
}
// iterator is not iterableのエラーが発生
// for(const x of iterator){
// console.log(x);
// }
for(const x of iterable){
console.log(x);
}
■ 文字列はイテラブルである
文字列がイテラブルであることを、下記のコードで確認できます。
const str = "イテラブル";
// 1. [Symbol.iterator]メソッドを持っているかチェック
console.log(typeof str[Symbol.iterator]); // "function"
// 2. イテレータの取得と値の取得
const iterator = str[Symbol.iterator]();
console.log(iterator.next()); // { value: "イ", done: false }
console.log(iterator.next()); // { value: "テ", done: false }
console.log(iterator.next()); // { value: "ラ", done: false }
console.log(iterator.next()); // { value: "ブ", done: false }
console.log(iterator.next()); // { value: "ル", done: false }
console.log(iterator.next()); // { value: undefined, done: true }
// 3.for...ofループによる反復処理
for(const x of str){
console.log(x); // イテラブル
}
JavaScriptのジェネレーターとジェネレーター関数
ジェネレーターの解説をする際、ジェネレーターとジェネレーター関数 という2つのキーワードがでてきます。少しややこしいので、初めにその関係性を説明します。
ジェネレータ関数は、特別な構文(function* 構文)で定義された関数であり、その結果生成されるのがジェネレーターです。
■ ジェネレーターとは
ジェネレーターとは、イテレーターかつイテラブルであるオブジェクトのことを指します。
-
イテレーターである:
next()メソッド
を持ち、値を生成することができます。 -
イテラブルである:
[Symbol.iterator]()メソッド
を持ち、自身を返すことができるため、イテラブルなオブジェクトとしても機能します。
■ ジェネレーター関数とは
ジェネレーター関数は、function*構文を使用して定義される関数です。この関数では、yieldキーワードを使用して、一時停止しながら複数の値を生成することができます。最初に呼び出されると、ジェネレーター関数はコードを実行せず、ジェネレーターと呼ばれるある種のイテレーターを返します。
// ジェネレータ関数 「function*」という構文を使用
function* generatorSample(){
yield 1;
yield 2;
yield 3;
}
// 呼び出されると実行されず、代わりにジェネレータオブジェクトが返されます。
const gen = generatorSample();
console.log(typeof gen);// object
console.log(Object.prototype.toString.call(gen)) // [object Generator]
// イテレーター
console.log(gen.next()); // { value: 1, done: false }
// イテラブル
for (const value of gen) {
console.log(value); // 2, 3
}
ジェネレーター関数は内部状態を保持することができるため、前回の呼び出し時の変数の値を引き続き使用することができます。
function* counter() {
let count = 0;
while (true) {
yield count++;
}
}
const gen = counter();
while ((result = gen.next()).value < 100) {
console.log(result.value); // 0から99まで出力
}