ES6 系列之 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

具體的執行過程就不說了,我們直接在 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

如果你想看到完整可用的代碼,你可以使用 regenerator,這是 facebook 下的一個工具,用於編譯 ES6 的 generator 函數。

我們先安裝一下 regenerator:

npm install -g regenerator

然後新建一個 generator.js 文件,裏面的代碼就是文章最一開始的代碼,我們執行命令:

regenerator --include-runtime generator.js > generator-es5.js

我們就可以在 generator-es5.js 文件看到編譯後的完整可用的代碼。

而這一編譯就編譯了 700 多行…… 編譯後的代碼可以查看 generator-es5.js

總之編譯後的代碼還蠻複雜,我們可以從中抽離出大致的邏輯,至少讓簡單編譯的那段代碼能夠跑起來。

mark 函數

簡單編譯後的代碼第一段是這樣的:

var _marked = /*#__PURE__*/ regeneratorRuntime.mark(helloWorldGenerator);

我們查看完整編譯版本中 mark 函數的源碼:

runtime.mark = function(genFun) {
  genFun.__proto__ = GeneratorFunctionPrototype;
  genFun.prototype = Object.create(Gp);
  return genFun;
};

這其中又涉及了 GeneratorFunctionPrototype 和 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";

這段代碼構建了一堆看起來很複雜的關係鏈,其實這是參照着 ES6 規範構建的關係鏈:

regenerator

圖中 +@@toStringTag:s = 'Generator' 的就是 Gp,+@@toStringTag:s = 'GeneratorFunction' 的就是 GeneratorFunctionPrototype。

構建關係鏈的目的在於判斷關係的時候能夠跟原生的保持一致,就比如:

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 添加這三個函數的方法:

// 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.prototype, outerFn.prototype 其實就是 genFun.prototypegenFun.prototype 是一個空對象,原型上有 next() 方法。

所以當你執行 hw.next() 的時候,執行的其實是 hw 原型的原型上的 next 函數,next 函數執行的又是 hw 的 _invoke 函數:

generator._invoke = makeInvokeMethod(innerFn, self, context);

innerFn 就是 wrap 包裹的那個函數,其實就是 helloWordGenerato$ 函數,吶,就是這個函數:

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 中又會執行 complete,執行完 complete,因爲 this.next = end 的緣故,再執行就會執行 stop 函數。

我們來看下 makeInvokeMethod 函數:

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() 的時候,其實執行了

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)

在 complete 中,因爲 record.type === "return"

this.rval = 'ending';
this.method = "return";
this.next = "end";

然後返回了全局對象 ContinueSentinel,其實就是一個全局空對象。

然後在 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 已經是 'completed' 的緣故,直接就返回 { 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());

ES6 系列

ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog

ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標籤模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模擬實現、模塊加載方案、異步處理等內容。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啓發,歡迎 star,對作者也是一種鼓勵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章