前言

本文就是简单介绍下 Generator 语法编译后的代码。

Generator

  1. function* helloWorldGenerator() {
  2. yield 'hello';
  3. yield 'world';
  4. return 'ending';
  5. }

我们打印下执行的结果:

  1. var hw = helloWorldGenerator();
  2. console.log(hw.next()); // {value: "hello", done: false}
  3. console.log(hw.next()); // {value: "world", done: false}
  4. console.log(hw.next()); // {value: "ending", done: true}
  5. console.log(hw.next()); // {value: undefined, done: true}

Babel

具体的执行过程就不说了,我们直接在 Babel 官网的 Try it out 粘贴上述代码,然后查看代码被编译成了什么样子:

  1. /**
  2. * 我们就称呼这个版本为简单编译版本吧
  3. */
  4. var _marked = /*#__PURE__*/ regeneratorRuntime.mark(helloWorldGenerator);
  5. function helloWorldGenerator() {
  6. return regeneratorRuntime.wrap(
  7. function helloWorldGenerator$(_context) {
  8. while (1) {
  9. switch ((_context.prev = _context.next)) {
  10. case 0:
  11. _context.next = 2;
  12. return "hello";
  13. case 2:
  14. _context.next = 4;
  15. return "world";
  16. case 4:
  17. return _context.abrupt("return", "ending");
  18. case 5:
  19. case "end":
  20. return _context.stop();
  21. }
  22. }
  23. },
  24. _marked,
  25. this
  26. );
  27. }

猛一看,好像编译后的代码还蛮少的,但是细细一看,编译后的代码肯定是不能用的呀,regeneratorRuntime 是个什么鬼?哪里有声明呀?mark 和 wrap 方法又都做了什么?
难道就不能编译一个完整可用的代码吗?

regenerator

如果你想看到完整可用的代码,你可以使用 regenerator,这是 facebook 下的一个工具,用于编译 ES6 的 generator 函数。
我们先安装一下 regenerator:

  1. npm install -g regenerator

然后新建一个 generator.js 文件,里面的代码就是文章最一开始的代码,我们执行命令:

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

我们就可以在 generator-es5.js 文件看到编译后的完整可用的代码。
而这一编译就编译了 700 多行…… 编译后的代码可以查看 generator-es5.js
总之编译后的代码还蛮复杂,我们可以从中抽离出大致的逻辑,至少让简单编译的那段代码能够跑起来。

mark 函数

简单编译后的代码第一段是这样的:

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

我们查看完整编译版本中 mark 函数的源码:

  1. runtime.mark = function(genFun) {
  2. genFun.__proto__ = GeneratorFunctionPrototype;
  3. genFun.prototype = Object.create(Gp);
  4. return genFun;
  5. };

这其中又涉及了 GeneratorFunctionPrototype 和 Gp 变量,我们也查看下对应的代码:

  1. function Generator() {}
  2. function GeneratorFunction() {}
  3. function GeneratorFunctionPrototype() {}
  4. ...
  5. var Gp = GeneratorFunctionPrototype.prototype =
  6. Generator.prototype = Object.create(IteratorPrototype);
  7. GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;
  8. GeneratorFunctionPrototype.constructor = GeneratorFunction;
  9. GeneratorFunctionPrototype[toStringTagSymbol] =
  10. GeneratorFunction.displayName = "GeneratorFunction";

这段代码构建了一堆看起来很复杂的关系链,其实这是参照着 ES6 规范构建的关系链:
12.ES6 系列之 Babel 将 Generator 编译成了什么样子 - 图1
图中 +@@toStringTag:s = ‘Generator’ 的就是 Gp,+@@toStringTag:s = ‘GeneratorFunction’ 的就是 GeneratorFunctionPrototype。
构建关系链的目的在于判断关系的时候能够跟原生的保持一致,就比如:

  1. function* f() {}
  2. var g = f();
  3. console.log(g.__proto__ === f.prototype); // true
  4. console.log(g.__proto__.__proto__ === f.__proto__.prototype); // true

为了简化起见,我们可以把 Gp 先设置为一个空对象,不过正如你在上图中看到的,next()、 throw()、return() 函数都是挂载在 Gp 对象上,实际上,在完整的编译代码中,确实有为 Gp 添加这三个函数的方法:

  1. // 117 行
  2. function defineIteratorMethods(prototype) {
  3. ["next", "throw", "return"].forEach(function(method) {
  4. prototype[method] = function(arg) {
  5. return this._invoke(method, arg);
  6. };
  7. });
  8. }
  9. // 406 行
  10. defineIteratorMethods(Gp);

为了简单起见,我们将整个 mark 函数简化为:

  1. runtime.mark = function(genFun) {
  2. var generator = Object.create({
  3. next: function(arg) {
  4. return this._invoke('next', arg)
  5. }
  6. });
  7. genFun.prototype = generator;
  8. return genFun;
  9. };