前言
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 是个什么鬼?哪里有声明呀?mark 和 wrap 方法又都做了什么?
难道就不能编译一个完整可用的代码吗?
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 规范构建的关系链:
图中 +@@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;
};