俺、サービス売って家買うんだ

Swift, Vue.js, 統計, GCP / このペースで作ってればいつか2-3億で売れるのがポっと出来るんじゃなかろうか

【今更ES2015を追ってみるシリーズ 4】Iterator編

f:id:ie-kau:20151019153301j:plain


2015年中に終わる気配が全く無いですが、不定期連載その4です。
今回はIteratorについて。

前回までの記事

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を全て返し終わっているかどうか

こんなイメージ
f:id:ie-kau:20151111015233p:plain

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. 1回目のfor..ofループ突入
  2. ①が呼ばれ1が戻り値としてコンソールに出力される
  3. 2回目のfor..ofループ突入
  4. ②が呼ばれiがインクリメントされる
  5. ③が呼ばれ2が戻り値としてコンソールに出力される
  6. 以下繰り返し

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の応用とかとて表現力高くて楽しそうですね ( ꒪⌓꒪)オラッオラッ

参考