ES2015のIteratorとGeneratorについて

最近はVue.jsを個人で使用しているので、javascriptについても学んでいます。

ES2015の機能を少しづつ勉強しています。

基礎能力がないと結局開発の仕方がわからないなってのがあるので、勉強がてらに記事を書きます。

Iteratorって何?

そもそもIteratorってなんぞや?って感じですよね。

イテレータ - Wikipedia

プログラミング言語的には、反復子と呼ばれているようです。

反復するためのものの意味で反復子(はんぷくし)と訳される。繰返子(くりかえし)という一般的ではない訳語もある。

繰り返されるものに対して、呼ばれるようですね。

反復子(反復可能なオブジェクト)に対して、javascriptfor..ofを使って処理ができるようです。

ということは・・・

配列

配列は反復可能なオブジェクトになります。

const programming_languages = ["javascript", "ruby", "php"]

for (let language of programming_languages) {
  console.log(language)
}
=>javascript
=>ruby
=>php

なるほど。

ただし、Iteratorオブジェクトではありません。

Iteratorはメソッドのnextを使用することができます。

Iteratorプロトコル

Iteratorであるためには、「Iteratorプロトコル」を実装する必要があります。

Symbol.iterator()

const programming_languages = ["javascript", "ruby", "php"]

const it = programming_languages[Symbol.iterator]()
console.log(it.next())
=>Object {value: "javascript", done: false}
console.log(it.next())
=>Object {value: "ruby", done: false}
console.log(it.next())
=>Object {value: "php", done: false}
console.log(it.next())
=>Object {value: undefined, done: true}

doneの真偽値で次があるかどうかがわかるんですね。

独自のIteratorを作成する

Logクラスを作成して、試してみます。

class Log {
  constructor() {
    this.messages = []
  }

  add(message) {
    const now = Date.now();
    console.log(`ログ追加:${message}(${now})`)
    return this.messages.push({ message, time: now })
  }

  [Symbol.iterator]() {
    let i = 0
    const messages = this.messages
    return {
      next: () => i >= messages.length ?
        { value: undefined, done: true } : { value: messages[i++], done: false }
    }
  }
}

const log = new Log()
log.add("foo")
log.add("bar")
log.add("baz")
console.log(log)
for (let l of log) {
  console.log(l)
=>Object {message: "foo", time: 1487771847145}
=>Object {message: "bar", time: 1487771847146}
=>Object {message: "baz", time: 1487771847147}
}

Iteratorを返すだけなら、既存のメソッドを使用した方が楽なので、既存のメソッドで書き換えます。

  [Symbol.iterator]() {
    return this.messages[Symbol.iterator]()
  }

結果は同じになります。

Generatorについて

ここでGeneratorが出てきます。

Generatorってなんぞや?って感じですよね。

関数の一種と捉えることができますが、通常の関数とは違います。

  • 関数は制御(と値)を任意の場所から呼び出し側に戻すことができる
  • Generatorを呼び出すときにはすぐには実行されず、まずはIteratorが戻される。そのあとで、Iteratorのメソッドnextを呼び出す度に実行が進む。

意味がわからないって感じなので、実際のコードと動きを見ていきます。

定義方法

Generatorを使用するにはfunctionの後に*を付けます。

呼び出し側はyieldを使用します。

rubyでもあるのに、ここでもあるのか!って思いました。

rubyの場合はブロックを返してくれるやつでしたね。

function* frame_work() {
  yield 'ruby on rails'
  yield 'vue'
  yield 'react'
}

const it = frame_work()
console.log(it.next())
=>Object {value: "ruby on rails", done: false}
console.log(it.next())
=>Object {value: "vue", done: false}
console.log(it.next())
=>Object {value: "react", done: false}
console.log(it.next())
=>Object {value: undefined, done: true}

確かにIteratorオブジェクトになっとる。

双方向コミュニケーション

yieldは式なので、評価の結果、何らかの値になります。

では、どんな値なのでしょうか。

next呼び出し時の引数の値になります。

どういうことだ?ってことで例をみます。

function* introduction() {
  const name = yield '名前は何ですか?'
  const hobby = yield '趣味は何ですか?'
  return `名前は${name}です。趣味は${hobby}です。`
}

const it = introduction()
console.log(it.next())
=>Object {value: "名前は何ですか?", done: false}
console.log(it.next('javascript'))
=>Object {value: "趣味は何ですか?", done: false}
console.log(it.next('プログラミング'))
Object {value: "名前はjavascriptです。趣味はプログラミングです。", done: true}

一個前の値を保持していますね。

この辺の遅延評価を利用して、非同期処理を行えるようですね。

どうやるんじゃろうか。

以上です。