JavaScript

IteratorとGenerator

More than 1 year has passed since last update.

社内LT用です。


Iteratorをおさらい

ary = [0, 1, 2];
it = ary[Symbol.iterator]()
console.log(it.next());
// { done: false, value: 0 }
console.log(it.next());
// { done: false, value: 1 }
console.log(it.next());
// { done: false, value: 2 }
console.log(it.next());
// { done: true, value: undefined }

for of構文によるループ

ary = [0, 1, 2];
it = ary[Symbol.iterator]();
for (value of it) {
  console.log(value);
  // 0, 1, 2
}

イテレーターとは、一連の処理中において現在の処理位置を把握しつつ、コレクション中の項目へ一つずつアクセスする方法を備えたオブジェクトのことです。

MDN イテレーターとジェネレーター より


JavaScript においては、イテレーターは一連の処理中の次の項目を返す next() メソッドを提供するオブジェクトです。このメソッドは done と value という 2 つのプロパティを持つオブジェクトを返します。

MDN イテレーターとジェネレーター より


next() を 実装してみる

const generateIterator = function() {
  let count = 0;
  return {
    next: function() {
      if (count > 2) return { done: true, value: void(0) };

      return { done: false, value: count++ }
    }
  }
}

const it = generateIterator();

console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

{ done: boolean, value: any } はIteratorResultオブジェクト


sampleIteratorは for of によるループでは利用できない


iterableになるために、オブジェクトは@@iteratorメソッドを実装する必要があります。これは、オブジェクト(または、prototype chainオブジェクトの一つ)がSymbol.iteratorキーのプロパティを持つ必要があります。:

MDN 反復処理プロトコル より


どうする

  • IteratorをさらにIterableにする必要がある
    • Iterator[Symbol.iterator]() を持つ必要がある
    • Iteratorそのものが返り値となる関数
    • ArrayやMapはもともとIterable

[Symbol.iterator]() を実装する

const generateIterator = function() {
  let count = 0;
  return {
    [Symbol.iterator]: function() {
      return this;
    },

    next: function() {
      if (count > 2) return { done: true, value: void(0) };

      return { done: false, value: count++ }      
    }
  }
};

const it = generateIterator();

// for of
for (value of it) {
  console.log(value);
  // 0, 1, 2
}

だるい

  • そこでGeneratorですよ

Generator

function* it() {
  let count = 0;
  while(true) {
    if (count > 2) break;
    yield count++;
  }
}

for (value of it()) {
  console.log(value);
  // 0, 1, 2
}

超スッキリ


ここが違う

  • functionキーワードの後に *
  • return の代わりに yield

yield

  • IteratorResultを返す
  • yieldに続く式を評価した結果が value に代入される
  • yield式によって関数は実行を停止する
    • next() が呼ばれると実行が再開する
    • 再開後 return もしくは何もせずに関数が終了した場合 done:true でIteratorResultが返る

Iteratorの利点

  • オブジェクトや関数をIterableにすることで for of に突っ込める
  • スプレッド演算子による式展開が行える
  • 分割代入が行える
  • range関数が簡単にかける

スプレッド演算子による式展開が行える

function* it() {
  yield "hoge";
  yield "hige";
  yield "fuga";
}

console.log(...it());
// hoge hige fuga


分割代入が行える

function* it() {
  yield* [0, 1, 2];
}

[a, b, c] = it();

console.log(a); // 0
console.log(b); // 1
console.log(c); // 2

range関数が簡単にかける

function* range(start, end) {
  let n = start;
  while(true) {
    yield n++;
    if (n > end) break;
  }
}

const r = [...range(3, 6)];
console.log(r); // [3, 4, 5, 6]

参考にさせていただいたページ