- Notifications
You must be signed in to change notification settings - Fork 4.7k
Description
前言
本文就是简单介绍下 Generator 语法编译后的代码。
Generator
function*helloWorldGenerator(){yield'hello';yield'world';return'ending';}我们打印下执行的结果:
varhw=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);functionhelloWorldGenerator(){returnregeneratorRuntime.wrap(functionhelloWorldGenerator$(_context){while(1){switch((_context.prev=_context.next)){case0: _context.next=2;return"hello";case2: _context.next=4;return"world";case4: return_context.abrupt("return","ending");case5: 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);returngenFun;};这其中又涉及了 GeneratorFunctionPrototype 和 Gp 变量,我们也查看下对应的代码:
functionGenerator(){}functionGeneratorFunction(){}functionGeneratorFunctionPrototype(){}...varGp=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(){}varg=f();console.log(g.__proto__===f.prototype);// trueconsole.log(g.__proto__.__proto__===f.__proto__.prototype);// true为了简化起见,我们可以把 Gp 先设置为一个空对象,不过正如你在上图中看到的,next()、 throw()、return() 函数都是挂载在 Gp 对象上,实际上,在完整的编译代码中,确实有为 Gp 添加这三个函数的方法:
// 117 行functiondefineIteratorMethods(prototype){["next","throw","return"].forEach(function(method){prototype[method]=function(arg){returnthis._invoke(method,arg);};});}// 406 行defineIteratorMethods(Gp);为了简单起见,我们将整个 mark 函数简化为:
runtime.mark=function(genFun){vargenerator=Object.create({next: function(arg){returnthis._invoke('next',arg)}});genFun.prototype=generator;returngenFun;};wrap 函数
除了设置关系链之外,mark 函数的返回值 genFun 还作为了 wrap 函数的第二个参数传入:
functionhelloWorldGenerator(){returnregeneratorRuntime.wrap(functionhelloWorldGenerator$(_context){ ... },_marked,this);}我们再看下 wrap 函数:
functionwrap(innerFn,outerFn,self){vargenerator=Object.create(outerFn.prototype);varcontext=newContext([]);generator._invoke=makeInvokeMethod(innerFn,self,context);returngenerator;}所以当执行 var hw = helloWorldGenerator(); 的时候,其实执行的是 wrap 函数,wrap 函数返回了 generator,generator 是一个对象,原型是 outerFn.prototype, outerFn.prototype 其实就是 genFun.prototype, genFun.prototype 是一个空对象,原型上有 next() 方法。
所以当你执行 hw.next() 的时候,执行的其实是 hw 原型的原型上的 next 函数,next 函数执行的又是 hw 的 _invoke 函数:
generator._invoke=makeInvokeMethod(innerFn,self,context);innerFn 就是 wrap 包裹的那个函数,其实就是 helloWordGenerato$ 函数,呐,就是这个函数:
functionhelloWorldGenerator$(_context){while(1){switch((_context.prev=_context.next)){case0: _context.next=2;return"hello";case2: _context.next=4;return"world";case4: return_context.abrupt("return","ending");case5: case"end": return_context.stop();}}}而 context 你可以直接理解为这样一个全局对象:
varContinueSentinel={};varcontext={done: false,method: "next",next: 0,prev: 0,abrupt: function(type,arg){varrecord={};record.type=type;record.arg=arg;returnthis.complete(record);},complete: function(record,afterLoc){if(record.type==="return"){this.rval=this.arg=record.arg;this.method="return";this.next="end";}returnContinueSentinel;},stop: function(){this.done=true;returnthis.rval;}};每次 hw.next 的时候,就会修改 next 和 prev 属性的值,当在 generator 函数中 return 的时候会执行 abrupt,abrupt 中又会执行 complete,执行完 complete,因为 this.next = end 的缘故,再执行就会执行 stop 函数。
我们来看下 makeInvokeMethod 函数:
varContinueSentinel={};functionmakeInvokeMethod(innerFn,self,context){varstate='start';returnfunctioninvoke(method,arg){if(state==='completed'){return{value: undefined,done: true};}context.method=method;context.arg=arg;while(true){state='executing';varrecord={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 对象:
varrecord={type: "normal",arg: innerFn.call(self,context)};而在 innerFn.call(self, context) 中,因为 _context.next 为 4 的缘故,其实执行了:
_context.abrupt("return",'ending');而在 abrupt 中,我们又构建了一个 record 对象:
varrecord={};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;returnthis.rval;// this.rval 其实就是 `ending`所以最终返回的值为:
{value: 'ending',done: true};之后,我们再执行 hw.next() 的时候,因为 state 已经是 'completed' 的缘故,直接就返回 {value: undefined, done: true}
不完整但可用的源码
当然这个过程,看文字理解起来可能有些难度,不完整但可用的代码如下,你可以断点调试查看具体的过程:
(function(){varContinueSentinel={};varmark=function(genFun){vargenerator=Object.create({next: function(arg){returnthis._invoke("next",arg);}});genFun.prototype=generator;returngenFun;};functionwrap(innerFn,outerFn,self){vargenerator=Object.create(outerFn.prototype);varcontext={done: false,method: "next",next: 0,prev: 0,abrupt: function(type,arg){varrecord={};record.type=type;record.arg=arg;returnthis.complete(record);},complete: function(record,afterLoc){if(record.type==="return"){this.rval=this.arg=record.arg;this.method="return";this.next="end";}returnContinueSentinel;},stop: function(){this.done=true;returnthis.rval;}};generator._invoke=makeInvokeMethod(innerFn,context);returngenerator;}functionmakeInvokeMethod(innerFn,context){varstate="start";returnfunctioninvoke(method,arg){if(state==="completed"){return{value: undefined,done: true};}context.method=method;context.arg=arg;while(true){state="executing";varrecord={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);functionhelloWorldGenerator(){returnregeneratorRuntime.wrap(functionhelloWorldGenerator$(_context){while(1){switch((_context.prev=_context.next)){case0: _context.next=2;return"hello";case2: _context.next=4;return"world";case4: return_context.abrupt("return","ending");case5: case"end": return_context.stop();}}},_marked,this);}varhw=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,对作者也是一种鼓励。
