— y2sunlight 2021-06-14
どんな言語でもコレクション内の各アイテムに対する反復処理は必須の機能です。JavaScriptでも言語コアに反復処理の機能が直接的に取り入れられています。この機能によって、for...of ループの標準的な動作とカスタマイズの仕組みを提供しています。
ES2015では反復処理の為に新しい仕組みとして、反復処理プロトコルが追加されました。このプロトコルには、次の2種類が含まれます。
これらのプロトコルを使って、イテレーターオブジェクト及びジェネレーターオブジェクトが定義されています。
イテレーターとは、コレクション内のオブジェクトを列挙する機能を持ったオブジェクトの事です。ES2015*2015で導入された for…of 構文は、このイテレーターを使用した構文糖(Syntax Sugar)です。
以下にイテレーターを使わないレトロな for 文と、イテレーターを使用した for 文を比べてみます。
let students = ['sato', 'suzuki', 'takahashi']; for(let i = 0; i < students.length; i++) { console.log(students[i]); }
let students = ['sato', 'suzuki', 'takahashi']; for(let student of students) { console.log(student); }
また、イテレーターオブジェクトを明示的に使用したコードは次の様になります。
let students = ['sato', 'suzuki', 'takahashi']; // イテレーターオブジェクトの取得 let it = students[Symbol.iterator](); let obj = it.next(); while(!obj.done()) { console.log(obj); obj = it.next(); }
Symbol.iteratorはコレクションからイテレーターを取得する為の特別なキーです。詳しくはこちらを参照して下さい。
ジェネレーターは数列などの列挙可能な値(オブジェクト)を、次々と生成し、yield
命令を使って呼び出し元に返す機構です。JavaScriptではES2015*2015から導入され、function*
命令(ジェネレーター関数)を使って使用することができます。
以下にジェネレーターの簡単な例を示します。
class Student { // コンストラクタの定義 constructor(name) { this.name = name; } // メソッドの定義 greeting() { console.log(`I am ${this.name}.`); } } function* getStudents() { yield new Student('Sato'); yield new Student('Suzuki'); yield new Student('Takahashi'); } for (let student of getStudents()) { student.greeting(); }
最初にジェネレーター関数が呼び出されると、コードは実行されずイテレーターオブジェクトが返されます。このオブジェクトのことをジェネレーターと呼びます。その後、呼び出し側でジェネレーターの next
命令が呼び出されると、ジェネレーター関数は最初の yield
までのコードを実行します。このようにしてジェネレーターの next
命令が呼び出される度に次々と yield
が実行されます。
別の例として、2000年から2100年までの閏年を計算するコードを、以下に示します。
function* genLeapYear(start_year) { let year = start_year; while(true) { if(isLeapYear(year)) { yield year; } year++; } } function isLeapYear(year) { return (year % 400 == 0) || (year % 100 != 0 && year % 4 == 0); } for (let year of genLeapYear(2000)) { if(year > 2100) break; console.log(year); }
上の例はジェネレーターがなぜ使われるのかを端的に示しています。この例から分かるように、ジェネレーターを使用することにより全ての計算結果が格納された配列を用意する必要がなくなります。また、計算の終了を関数の呼び出し側で行うこともできます。