Generator のプロトタイプ

Firefox 26 (Nightly) で function * () { yield ... }ECMAScript 6th にある Generator の構文が導入された。

ということで、やる気を出して、チョコチョコと調査。

まず、用語の定義

GeneratorFunctionインスタンス
function *() { yield "OK"; } の様な関数オブジェクト
GeneratorObject
GeneratorFunctionから生成されたインスタンスオブジェクト
[[prototype]]
オブジェクトのプロトタイプチェーンとなる内部プロパティ
// GeneratorFunction
function * geneFunc () {
  yield "OK";
}

// GeneratorObject
var geneObj = geneFunc();

GeneratorFunctionインスタンスの prototype プロパティ

GeneratorFunctionインスタンスは関数オブジェクトなので、prototype プロパティを持つ。が、このプロパティは普通に生成された関数オブジェクトのprototypeとは異なるものである。

通常の関数オブジェクトのprototypeは、その関数オブジェクトをconstructorプロパティに持ち、[[prototype]]にObject.prototypeを持つ普通のオブジェクトだった。
GeneratorFunctionのprototypeは、仕様(Draft)では%GeneratorPrototype%と称される、[#91;prototype]]にGenerator用のメソッドなどを備えたオブジェクトとなる。

GeneratorObject のプロトタイプチェーン

GeneratorFunctionインスタンスを実行すると、GeneratorObjectが生成されるが、この生成されたオブジェクトのプロトタイプチェーンにはGeneratorFunctionインスタンスのprototypeが使用される。これは、geneObj instanceof geneFunc => true であることを意味する。
簡単に言うと普通に new したのと同様の仕組み。

Generator構文は、*を付けるなど、特殊な関数ではあるが、内部的にはプロトタイプの仕組みを用いたコンストラクタ関数を作っていると思えば、そんなに特殊ではないようにも思えてくる。

プロトタイプを弄る

Generatorのプロトタイプの仕組みを知ると、これを弄りたくなるのが人情だろう(ぉ

Generatorのnextメソッドをオーバーライドしてみよう。
nextメソッドなどは、GeneratorFunction.prototype.__proto__.next になるので、GeneratorFunction.prototype.next を定義してあげれば、オーバーライド可能である。

var gene = function * () {
  yield "OK";
};

gene.prototype.value = 0;
// next メソッドは configurable: false, writable: false なので単純な代入は不可能
Object.defineProperty(gene.prototype, "next", {
  value: function () {
    this.value++;
    if (this.value < 4) {
      return this.value;
    }
    throw StopIteration;
  },
});

for (var v of gene()) {
  console.log(v);
}
// 1
// 2
// 3

Firefox Nightly では意図通り動いたが、Chrome Canary ではダメだった。なんか実装が違う...。StopIteration が存在しないし...。どうなってるの...?