【今更ES2015を追ってみるシリーズ 4】Iterator編
2015年中に終わる気配が全く無いですが、不定期連載その4です。
今回はIteratorについて。
前回までの記事
- 【今更ES2015を追ってみるシリーズ 1】Promise編 - 俺、サービス売って家買うんだ
- 【今更ES2015を追ってみるシリーズ 2】Proxy編 - 俺、サービス売って家買うんだ
- 【今更ES2015を追ってみるシリーズ 3】Symbol編 - 俺、サービス売って家買うんだ
Iteratorとは?
Iteratorとは、ArrayやStringなどループで回して要素に一つづつアクセスできるオブジェクトで、且つ、ループで順に回していく際に今どこにいるかを追跡できるものです。ES2015からArrayやStringなどループできるオブジェクトは抽象化されIterableというインターフェイスを実装する形で再定義されています。
また、ES2015からはfor..of文という構文でItrableなオブジェクトをループすることが可能になっています。今までのように通常のfor文やfor..in文で回す必要はなくなります。
使い方
let arr = [1, 2, 3]; // for..in文 for (let i in arr) { console.log(i); // 0, 1, 2 } // for..of文 for (let i of arr) { console.log(i); // 1, 2, 3 }
for..inだと当然ですがキーが出力されます。
Iteratorオブジェクト
Iteratorオブジェクトはnextをいうメソッドを持っています。nextはコールすると、戻り値としてvalue,doneという2つのキーを持つオブジェクトを返します。
- value
- 次の値
- done
- Iterableオブジェクトのvalueを全て返し終わっているかどうか
こんなイメージ
Iteratorを取得する
以下のようにWell-known SymbolsであるSymbol.iteratorをIterableなオブジェクトのキーとして実行するとIteratorを取得することができます。
var arr = ['a', 'b', 'c']; var iter = arr[Symbol.iterator]();
では一度実行してみましょう。
iter.next(); // Object {value: "a", done: false}
続けて数回実行してみましょう。
iter.next(); // Object {value: "b", done: false} iter.next(); // Object {value: "c", done: false} iter.next(); // Object {value: undefined, done: true}
valueがなくなくなったタイミングでdone がtrueになりますね。
独自のIteratorの作成
応用でオブジェクトに対してSymbol.iteratorをキーにしてvalueとdoneを返すnextという名前のメソッドを返すメソッドを実装すればIterableになりfor..of文が使えるようになります。
var obj = { a: 1, b: 2, c: 3, [Symbol.iterator]() { var i = 0; var keys = Object.keys(this); var keyLen = keys.length; var that = this; return { next() { var ret = (i < keyLen) ? {value: that[keys[i]], done: false} : {value: undefined, done: true}; i++; return ret; } } } } for (var i of obj) { console.log(i); // 1, 2, 3 }
しかし、長い.....ので。。
Generator
Generatorは簡単にIteratorを作るくメソッドです。 上で書いたオブジェクトをGeneratorを利用して書き直してみましょう。実装方法は簡単でfunctionキーワードの次に*をつければGeneratorになります。
Generatorをつかて独自のIteratorを書く
var obj = { a: 1, b: 2, c: 3, [Symbol.iterator]: function* () { var i = 0; var keys = Object.keys(this); var keyLen = keys.length; yield this[keys[i]]; // ...① i++; // ...② yield this[keys[i]]; // ...③ i++ yield this[keys[i]]; } } for (var i of obj) { console.log(i); // 1, 2, 3 }
挙動を確認したいため意図的に冗長な書き方をしています。
yieldはreturnみたいなものですが、コールされるたびに、その位置で処理を一旦止めます。
サンプルで流れを追うと、
- 1回目のfor..ofループ突入
- ①が呼ばれ1が戻り値としてコンソールに出力される
- 2回目のfor..ofループ突入
- ②が呼ばれiがインクリメントされる
- ③が呼ばれ2が戻り値としてコンソールに出力される
- 以下繰り返し
whileを使うとキー数が可変のオブジェクトに対応できます。
var obj = { a: 1, b: 2, c: 3, [Symbol.iterator]: function* () { var i = 0; var keys = Object.keys(this); var keyLen = keys.length; while (i < keyLen) { yield this[keys[i]]; i++; } } for (var i of obj) { console.log(i); // 1, 2, 3 }
また、この機能をつかって非同期を綺麗にかく方法もあるようなのでそれはまた今度勉強することにします。
まとめ
- for..ofループでIterableなオブジェクトの要素にアクセスする方法に秩序がもたらされた
- yieldは最後にコールされた状態を覚えておいてくれる
yieldの応用とかとて表現力高くて楽しそうですね ( ꒪⌓꒪)オラッオラッ