ES 6シリーズのBabelはGeneratorをどのようにコンパイルしましたか?


前言
本文は簡単にGenerator文法のコンパイル後のコードを紹介します.
Generator
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
私たちが実行した結果を印刷します.
var hw = helloWorldGenerator();

console.log(hw.next()); // {value: "hello", done: false}
console.log(hw.next()); // {value: "world", done: false}
console.log(hw.next()); // {value: "ending", done: true}
console.log(hw.next()); // {value: undefined, done: true}
ベベル
具体的な実行過程は言わないで、私達は直接にBabel公式サイトのTry it outで上述のコードを貼り付けて、コードがコンパイルされてどのような形になったかを確認します.
/**
 *                  
 */
var _marked = /*#__PURE__*/ regeneratorRuntime.mark(helloWorldGenerator);

function helloWorldGenerator() {
  return regeneratorRuntime.wrap(
    function helloWorldGenerator$(_context) {
      while (1) {
        switch ((_context.prev = _context.next)) {
          case 0:
            _context.next = 2;
            return "hello";

          case 2:
            _context.next = 4;
            return "world";

          case 4:
            return _context.abrupt("return", "ending");

          case 5:
          case "end":
            return _context.stop();
        }
      }
    },
    _marked,
    this
  );
}
猛さんはコンパイルしたコードはまだ少ないようですが、よく見てください.コンパイルしたコードはきっと使えません.regeneratorRuntimeは何の鬼ですか?どこに声明がありますか?markwrapの方法は何をしましたか?
完全に使えるコードをコンパイルできませんか?
レゲナート
完全に利用可能なコードを見たいなら、regeneratorを使ってもいいです.これはフェースブックの下のツールで、ES 6をコンパイルするためのgenerator関数です.
まずregeneratorをインストールします.
npm install -g regenerator
そして、新しいgenerator.jsファイルを作成します.コードは文章の最初のコードです.命令を実行します.
regenerator --include-runtime generator.js > generator-es5.js
私たちはgenerator-s 5.jsファイルでコンパイル後の完全な利用可能なコードを見ることができます.
このコンパイルは700行以上の行をコンパイルしました.コンパイルしたコードはgenerator-s 5.jsを見ることができます.
つまり、コンパイルしたコードはかなり複雑です.中から大体のロジックを抜き出して、少なくとも簡単にコンパイルしたコードを走らせます.
マルク関数
簡単なコンパイル後のコードの第一段はこうです.
var _marked = /*#__PURE__*/ regeneratorRuntime.mark(helloWorldGenerator);
完全コンパイルバージョンのmark関数のソースコードを確認します.
runtime.mark = function(genFun) {
  genFun.__proto__ = GeneratorFunctionPrototype;
  genFun.prototype = Object.create(Gp);
  return genFun;
};
この中にはGenerantorFunction ProttypeとGp変数が含まれています.対応するコードも確認してみます.
function Generator() {}
function GeneratorFunction() {}
function GeneratorFunctionPrototype() {}

...

var Gp = GeneratorFunctionPrototype.prototype =
  Generator.prototype = Object.create(IteratorPrototype);

GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;

GeneratorFunctionPrototype.constructor = GeneratorFunction;

GeneratorFunctionPrototype[toStringTagSymbol] =
  GeneratorFunction.displayName = "GeneratorFunction";
このコードは複雑に見える一連の関係チェーンを構築していますが、これはES 6仕様を参照して構築された関係チェーンです.
+@@toStringTag:s = 'Generator'はGpで、+@@toStringTag:s = 'GeneratorFunction'はGeneratonctionProttypeである.
関係チェーンを構築する目的は、関係を判断する時に原生と一致するようにすることです.
function* f() {}
var g = f();
console.log(g.__proto__ === f.prototype); // true
console.log(g.__proto__.__proto__ === f.__proto__.prototype); // true
簡略化のために、Gpを先に空のオブジェクトに設定できますが、上の図で見たように、next()、throw()、return()関数はすべてGpオブジェクトにマウントしています.実際には、完全なコンパイルコードの中で、Gpにこの3つの関数を追加する方法があります.
// 117  
function defineIteratorMethods(prototype) {
  ["next", "throw", "return"].forEach(function(method) {
    prototype[method] = function(arg) {
      return this._invoke(method, arg);
    };
  });
}

// 406  
defineIteratorMethods(Gp);
簡単のために、mark関数全体を簡略化しました.
runtime.mark = function(genFun) {
  var generator = Object.create({
    next: function(arg) {
      return this._invoke('next', arg)
    }
  });
  genFun.prototype = generator;
  return genFun;
};
wrap関数
リレーションチェーンの設定に加えて、mark関数の戻り値genFunは、wrap関数の第二のパラメータとしても導入されている.
function helloWorldGenerator() {
  return regeneratorRuntime.wrap(
    function helloWorldGenerator$(_context) {
      ...
    },
    _marked,
    this
  );
}
私たちはまたwrap関数を見ます.
function wrap(innerFn, outerFn, self) {
  var generator = Object.create(outerFn.prototype);
  var context = new Context([]);
  generator._invoke = makeInvokeMethod(innerFn, self, context);

  return generator;
}
したがって、var hw = helloWorldGenerator();を実行すると、実際にはwrap関数が実行され、wrap関数はgeneratorに戻り、generatorはオブジェクトであり、原型はouterFn.prototypeouterFn.prototypegenFun.prototypegenFun.prototypeは空のオブジェクトであり、原型にはnext()方法がある.hw.next()を実行すると、hwプロトタイプのnext関数が実行されます.next関数はhwの_です.invoke関数:
generator._invoke = makeInvokeMethod(innerFn, self, context);
innerFnはwrap小包のその関数です.実はhello Word Generatooの関数です.ねえ、この関数です.
function helloWorldGenerator$(_context) {
  while (1) {
    switch ((_context.prev = _context.next)) {
      case 0:
        _context.next = 2;
        return "hello";

      case 2:
        _context.next = 4;
        return "world";

      case 4:
        return _context.abrupt("return", "ending");

      case 5:
      case "end":
        return _context.stop();
    }
  }
}
contextは直接にこのようなグローバルオブジェクトとして理解できます.
var ContinueSentinel = {};

var context = {
  done: false,
  method: "next",
  next: 0,
  prev: 0,
  abrupt: function(type, arg) {
    var record = {};
    record.type = type;
    record.arg = arg;

    return this.complete(record);
  },
  complete: function(record, afterLoc) {
    if (record.type === "return") {
      this.rval = this.arg = record.arg;
      this.method = "return";
      this.next = "end";
    }

    return ContinueSentinel;
  },
  stop: function() {
    this.done = true;
    return this.rval;
  }
};
hw.nextのたびに、nextとprev属性の値が修正され、generator関数でreturnするとabruptが実行され、abruptではまたcompletteeが実行され、completteeが実行されます.this.next = endのために、stop関数が実行されます.
make Invoke Method関数を見に来ました.
var ContinueSentinel = {};

function makeInvokeMethod(innerFn, self, context) {
  var state = 'start';

  return function invoke(method, arg) {

    if (state === 'completed') {
      return { value: undefined, done: true };
    }

    context.method = method;
    context.arg = arg;

    while (true) {

      state = 'executing';

      var record = {
        type: 'normal',
        arg: innerFn.call(self, context)
      };
      if (record.type === "normal") {

        state = context.done
          ? 'completed'
          : 'yield';

        if (record.arg === ContinueSentinel) {
          continue;
        }

        return {
          value: record.arg,
          done: context.done
        };

      }
    }
  };
}
基本的な実行過程は分析しないで、第三回の実行hw.next()を重点的に見る時:hw.next()を3回目に実行した時、実行しました.
this._invoke("next", undefined);
invoke関数においてrecordオブジェクトを構築した:
var record = {
  type: "normal",
  arg: innerFn.call(self, context)
};
innerFn.call(self, context)では、_のためにcontext.nextは4のため、実行されました.
_context.abrupt("return", 'ending');
abruptでは、recordオブジェクトを構築しました.
var record = {};
record.type = 'return';
record.arg = 'ending';
その後、this.complete(record)が実行され、
completteでは、record.type === "return"からです.
this.rval = 'ending';
this.method = "return";
this.next = "end";
そして、グローバルオブジェクトContinue Sentinelに戻ってきました.
その後、invoke関数では、record.arg === ContinueSentinelのために、後のreturn文が実行されないまま、次のループに進みます.
そこで、再度innerFn.call(self, context)を実行したが、_context.nextはendであり、_context.stop()を実行し、stop関数において:
this.done = true;
return this.rval; // this.rval      `ending`
最終的に返される値は以下の通りです.
{
  value: 'ending',
  done: true
};
その後、hw.next()を実行する時、stateはすでに「completted」であるため、直接{ value: undefined, done: true}に戻ります.
不完全ですが、利用可能なソース
もちろんこのプロセスは、テキストを見て理解するのは難しいかもしれませんが、完全ではないですが、利用可能なコードは以下の通りです.
(function() {
  var ContinueSentinel = {};

  var mark = function(genFun) {
    var generator = Object.create({
      next: function(arg) {
        return this._invoke("next", arg);
      }
    });
    genFun.prototype = generator;
    return genFun;
  };

  function wrap(innerFn, outerFn, self) {
    var generator = Object.create(outerFn.prototype);

    var context = {
      done: false,
      method: "next",
      next: 0,
      prev: 0,
      abrupt: function(type, arg) {
        var record = {};
        record.type = type;
        record.arg = arg;

        return this.complete(record);
      },
      complete: function(record, afterLoc) {
        if (record.type === "return") {
          this.rval = this.arg = record.arg;
          this.method = "return";
          this.next = "end";
        }

        return ContinueSentinel;
      },
      stop: function() {
        this.done = true;
        return this.rval;
      }
    };

    generator._invoke = makeInvokeMethod(innerFn, context);

    return generator;
  }

  function makeInvokeMethod(innerFn, context) {
    var state = "start";

    return function invoke(method, arg) {
      if (state === "completed") {
        return { value: undefined, done: true };
      }

      context.method = method;
      context.arg = arg;

      while (true) {
        state = "executing";

        var record = {
          type: "normal",
          arg: innerFn.call(self, context)
        };

        if (record.type === "normal") {
          state = context.done ? "completed" : "yield";

          if (record.arg === ContinueSentinel) {
            continue;
          }

          return {
            value: record.arg,
            done: context.done
          };
        }
      }
    };
  }

  window.regeneratorRuntime = {};

  regeneratorRuntime.wrap = wrap;
  regeneratorRuntime.mark = mark;
})();

var _marked = regeneratorRuntime.mark(helloWorldGenerator);

function helloWorldGenerator() {
  return regeneratorRuntime.wrap(
    function helloWorldGenerator$(_context) {
      while (1) {
        switch ((_context.prev = _context.next)) {
          case 0:
            _context.next = 2;
            return "hello";

          case 2:
            _context.next = 4;
            return "world";

          case 4:
            return _context.abrupt("return", "ending");

          case 5:
          case "end":
            return _context.stop();
        }
      }
    },
    _marked,
    this
  );
}

var hw = helloWorldGenerator();

console.log(hw.next());
console.log(hw.next());
console.log(hw.next());
console.log(hw.next());
ES 6シリーズ
ES 6シリーズのディレクトリアドレス:https://github.com/mqyqingfeng/Blog
ES 6シリーズは二十編ぐらい書く予定です.ES 6部分の知識点の理解を深めるために、ブロックレベルの作用領域、ラベルテンプレート、矢印関数、Symbol、Set、Map及びPromiseのシミュレーション実現、モジュールロード方案、非同期処理などの内容を重点的に説明します.
間違いや不備があったら、ぜひ指摘してください.ありがとうございます.好きだったり、何かを啓発したりすれば、starを歓迎し、作者に対しても励みになります.