前言

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

Generator

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

我们打印下执行的结果:

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

Babel

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

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

猛一看,好像编译后的代码还蛮少的,但是细细一看,编译后的代码肯定是不能用的呀,regeneratorRuntime 是个什么鬼?哪里有声明呀?markwrap 方法又都做了什么?

难道就不能编译一个完整可用的代码吗?

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. ...
  6.  
  7. var Gp = GeneratorFunctionPrototype.prototype =
  8. Generator.prototype = Object.create(IteratorPrototype);
  9.  
  10. GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;
  11.  
  12. GeneratorFunctionPrototype.constructor = GeneratorFunction;
  13.  
  14. GeneratorFunctionPrototype[toStringTagSymbol] =
  15. GeneratorFunction.displayName = "GeneratorFunction";

这段代码构建了一堆看起来很复杂的关系链,其实这是参照着 ES6 规范构建的关系链:

regenerator

图中 +@@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.  
  10. // 406 行
  11. 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. };

wrap 函数

除了设置关系链之外,mark 函数的返回值 genFun 还作为了 wrap 函数的第二个参数传入:

  1. function helloWorldGenerator() {
  2. return regeneratorRuntime.wrap(
  3. function helloWorldGenerator$(_context) {
  4. ...
  5. },
  6. _marked,
  7. this
  8. );
  9. }

我们再看下 wrap 函数:

  1. function wrap(innerFn, outerFn, self) {
  2. var generator = Object.create(outerFn.prototype);
  3. var context = new Context([]);
  4. generator._invoke = makeInvokeMethod(innerFn, self, context);
  5.  
  6. return generator;
  7. }

所以当执行 var hw = helloWorldGenerator(); 的时候,其实执行的是 wrap 函数,wrap 函数返回了 generator,generator 是一个对象,原型是 outerFn.prototype, outerFn.prototype 其实就是 genFun.prototypegenFun.prototype 是一个空对象,原型上有 next() 方法。

所以当你执行 hw.next() 的时候,执行的其实是 hw 原型的原型上的 next 函数,next 函数执行的又是 hw 的 _invoke 函数:

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

innerFn 就是 wrap 包裹的那个函数,其实就是 helloWordGenerato$ 函数,呐,就是这个函数:

  1. function helloWorldGenerator$(_context) {
  2. while (1) {
  3. switch ((_context.prev = _context.next)) {
  4. case 0:
  5. _context.next = 2;
  6. return "hello";
  7.  
  8. case 2:
  9. _context.next = 4;
  10. return "world";
  11.  
  12. case 4:
  13. return _context.abrupt("return", "ending");
  14.  
  15. case 5:
  16. case "end":
  17. return _context.stop();
  18. }
  19. }
  20. }

而 context 你可以直接理解为这样一个全局对象:

  1. var ContinueSentinel = {};
  2.  
  3. var context = {
  4. done: false,
  5. method: "next",
  6. next: 0,
  7. prev: 0,
  8. abrupt: function(type, arg) {
  9. var record = {};
  10. record.type = type;
  11. record.arg = arg;
  12.  
  13. return this.complete(record);
  14. },
  15. complete: function(record, afterLoc) {
  16. if (record.type === "return") {
  17. this.rval = this.arg = record.arg;
  18. this.method = "return";
  19. this.next = "end";
  20. }
  21.  
  22. return ContinueSentinel;
  23. },
  24. stop: function() {
  25. this.done = true;
  26. return this.rval;
  27. }
  28. };

每次 hw.next 的时候,就会修改 next 和 prev 属性的值,当在 generator 函数中 return 的时候会执行 abrupt,abrupt 中又会执行 complete,执行完 complete,因为 this.next = end 的缘故,再执行就会执行 stop 函数。

我们来看下 makeInvokeMethod 函数:

  1. var ContinueSentinel = {};
  2.  
  3. function makeInvokeMethod(innerFn, self, context) {
  4. var state = 'start';
  5.  
  6. return function invoke(method, arg) {
  7.  
  8. if (state === 'completed') {
  9. return { value: undefined, done: true };
  10. }
  11.  
  12. context.method = method;
  13. context.arg = arg;
  14.  
  15. while (true) {
  16.  
  17. state = 'executing';
  18.  
  19. var record = {
  20. type: 'normal',
  21. arg: innerFn.call(self, context)
  22. };
  23. if (record.type === "normal") {
  24.  
  25. state = context.done
  26. ? 'completed'
  27. : 'yield';
  28.  
  29. if (record.arg === ContinueSentinel) {
  30. continue;
  31. }
  32.  
  33. return {
  34. value: record.arg,
  35. done: context.done
  36. };
  37.  
  38. }
  39. }
  40. };
  41. }

基本的执行过程就不分析了,我们重点看第三次执行 hw.next() 的时候:

第三次执行 hw.next() 的时候,其实执行了

  1. this._invoke("next", undefined);

我们在 invoke 函数中构建了一个 record 对象:

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

而在 innerFn.call(self, context) 中,因为 _context.next 为 4 的缘故,其实执行了:

  1. _context.abrupt("return", 'ending');

而在 abrupt 中,我们又构建了一个 record 对象:

  1. var record = {};
  2. record.type = 'return';
  3. record.arg = 'ending';

然后执行了 this.complete(record)

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

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

然后返回了全局对象 ContinueSentinel,其实就是一个全局空对象。

然后在 invoke 函数中,因为 record.arg === ContinueSentinel 的缘故,没有执行后面的 return 语句,就直接进入下一个循环。

于是又执行了一遍 innerFn.call(self, context),此时 _context.next 为 end, 执行了 _context.stop(), 在 stop 函数中:

  1. this.done = true;
  2. return this.rval; // this.rval 其实就是 `ending`

所以最终返回的值为:

  1. {
  2. value: 'ending',
  3. done: true
  4. };

之后,我们再执行 hw.next() 的时候,因为 state 已经是 'completed' 的缘故,直接就返回 { value: undefined, done: true}

不完整但可用的源码

当然这个过程,看文字理解起来可能有些难度,不完整但可用的代码如下,你可以断点调试查看具体的过程:

  1. (function() {
  2. var ContinueSentinel = {};
  3.  
  4. var mark = function(genFun) {
  5. var generator = Object.create({
  6. next: function(arg) {
  7. return this._invoke("next", arg);
  8. }
  9. });
  10. genFun.prototype = generator;
  11. return genFun;
  12. };
  13.  
  14. function wrap(innerFn, outerFn, self) {
  15. var generator = Object.create(outerFn.prototype);
  16.  
  17. var context = {
  18. done: false,
  19. method: "next",
  20. next: 0,
  21. prev: 0,
  22. abrupt: function(type, arg) {
  23. var record = {};
  24. record.type = type;
  25. record.arg = arg;
  26.  
  27. return this.complete(record);
  28. },
  29. complete: function(record, afterLoc) {
  30. if (record.type === "return") {
  31. this.rval = this.arg = record.arg;
  32. this.method = "return";
  33. this.next = "end";
  34. }
  35.  
  36. return ContinueSentinel;
  37. },
  38. stop: function() {
  39. this.done = true;
  40. return this.rval;
  41. }
  42. };
  43.  
  44. generator._invoke = makeInvokeMethod(innerFn, context);
  45.  
  46. return generator;
  47. }
  48.  
  49. function makeInvokeMethod(innerFn, context) {
  50. var state = "start";
  51.  
  52. return function invoke(method, arg) {
  53. if (state === "completed") {
  54. return { value: undefined, done: true };
  55. }
  56.  
  57. context.method = method;
  58. context.arg = arg;
  59.  
  60. while (true) {
  61. state = "executing";
  62.  
  63. var record = {
  64. type: "normal",
  65. arg: innerFn.call(self, context)
  66. };
  67.  
  68. if (record.type === "normal") {
  69. state = context.done ? "completed" : "yield";
  70.  
  71. if (record.arg === ContinueSentinel) {
  72. continue;
  73. }
  74.  
  75. return {
  76. value: record.arg,
  77. done: context.done
  78. };
  79. }
  80. }
  81. };
  82. }
  83.  
  84. window.regeneratorRuntime = {};
  85.  
  86. regeneratorRuntime.wrap = wrap;
  87. regeneratorRuntime.mark = mark;
  88. })();
  89.  
  90. var _marked = regeneratorRuntime.mark(helloWorldGenerator);
  91.  
  92. function helloWorldGenerator() {
  93. return regeneratorRuntime.wrap(
  94. function helloWorldGenerator$(_context) {
  95. while (1) {
  96. switch ((_context.prev = _context.next)) {
  97. case 0:
  98. _context.next = 2;
  99. return "hello";
  100.  
  101. case 2:
  102. _context.next = 4;
  103. return "world";
  104.  
  105. case 4:
  106. return _context.abrupt("return", "ending");
  107.  
  108. case 5:
  109. case "end":
  110. return _context.stop();
  111. }
  112. }
  113. },
  114. _marked,
  115. this
  116. );
  117. }
  118.  
  119. var hw = helloWorldGenerator();
  120.  
  121. console.log(hw.next());
  122. console.log(hw.next());
  123. console.log(hw.next());
  124. console.log(hw.next());

ES6 系列

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

ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。