regenerator的成果
regenerator是一个es5环境下实现es6的generator的形式,
regenerator 分为编译时和运行时两块,编译工具会负责将generator编译成es5,比方:
这段代码:
const len = 100;function *gen() { console.log('gen !'); for (let i = 0; i < len; i++) { yield i; } return 'what';}const g = gen();for (let i = 0; i < len; i++) { console.log(g.next());}
会被编译成
'use strict';var _marked =/*#__PURE__*/regeneratorRuntime.mark(gen);var len = 10;function gen() { var i; return regeneratorRuntime.wrap(function gen$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: console.log('gen !'); i = 0; case 2: if (!(i < len)) { _context.next = 7; break; } _context.next = 5; return i++; case 5: _context.next = 2; break; case 7: return _context.abrupt("return", 'what'); case 8: case "end": return _context.stop(); } } }, _marked);}var g = gen();for (var i = 0; i < len; i++) { console.log(g.next()); }
其中的regeneratorRuntime就是regenerator的运行时局部。
编译后产生的 switch
代码能够了解为定义的各个状态,以及状态间转化的逻辑
运行时
运行时咱们次要看一下regeneratorRuntime这个对象提供的内容。regeneratorRuntime的源代码地址
regeneratorRumtime在下面编译后的代码里,提供了两个办法,wrap
和 mark
wrap
wrap办法会返回一个generator对象,也就是合乎es6规范的迭代器,蕴含办法:
- next
- throw
- return
依照迭代器的定义,调用next
办法会是迭代器从上次yield的地位前面继续执行。每调用一次next
办法,都会调用一次编译后的gen$
办法。$gen
办法内会批改context上下文中的next值,达到一个驱动迭代器的成果。
gen$
这个函数很像是一个reducer
// line 274if (context.method === "next") { // Setting context._sent for legacy support of Babel's // function.sent implementation. context.sent = context._sent = context.arg;} //...// line 293var record = tryCatch(innerFn, self, context);if (record.type === "normal") { // If an exception is thrown from innerFn, we leave state === // GenStateExecuting and loop back for another invocation. state = context.done ? GenStateCompleted : GenStateSuspendedYield; if (record.arg === ContinueSentinel) { continue; } return { value: record.arg, done: context.done };} // ...
这里的innerFn就是之前的 gen$
函数,tryCatch(innerFn, self, context)
就会调用 gen$
函数,gen$
会返回一个后果,如果没有异样,就会返回next
办法的规范返回 { value: any, done: boolean }
tryCatch
办法 会捕捉 gen$
中抛出的异样。如果有捕捉到,则会返回异样的状态
// line 61function tryCatch(fn, obj, arg) { try { return { type: "normal", arg: fn.call(obj, arg) }; } catch (err) { return { type: "throw", arg: err }; }}
mark
mark办法会将gen
迭代器函数的原型指向runtime外部定义的generator对象。
那么regenerator外部是怎么定义generator对象的呢?
// line 82 function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} // This is a polyfill for %IteratorPrototype% for environments that // don't natively support it. var IteratorPrototype = {}; IteratorPrototype[iteratorSymbol] = function () { return this; }; var getProto = Object.getPrototypeOf; var NativeIteratorPrototype = getProto && getProto(getProto(values([]))); if (NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) { // This environment has a native %IteratorPrototype%; use it instead // of the polyfill. IteratorPrototype = NativeIteratorPrototype; } var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype; GeneratorFunctionPrototype.constructor = GeneratorFunction; GeneratorFunction.displayName = define( GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction" );// line 111// line 115 function defineIteratorMethods(prototype) { ["next", "throw", "return"].forEach(function(method) { define(prototype, method, function(arg) { return this._invoke(method, arg); }); }); }// line 406 defineIteratorMethods(Gp);
line 82 - line 111 定义了几个dummy constructor,并且定义了它们之间原型链关系,这些关系的设置是依照 ES6 标准 来设置的。
line 115 defineIteratorMethods 申明了一个定义generator外部办法的函数,函数中 define
办法外部理论是 Object.define
办法的封装。
line 406 应用 defineIteratorMethods,传入Gp,使得Gp成为一个generator对象。defineIteratorMethods定义了next, throw, return 三个办法
context
generator的上下文,即状态机的上下文,提供了多个办法和变量,《runtime.js#L521》
办法:
- reset
- stop
- dispatchException
- abrupt
- complete
- finish
- catch
变量:这些变量的初始化都在reset办法里,reset办法会在构造函数外面被调用
- next number - 状态机的下一个状态值
- pre number - 状态机的上一个状态值
- sent
- done
- delegate
- method
- arg
编译时
编译时,迭代器里的内容会被编译成switch...case模式的代码,其中各个case根本是以yield关键词作为划分case的根据,而后整个switch...case会被包裹在一个while(1)的循环里,只有当某一个case应用return返回时才会跳出while循环。
比方一个简略的,可迭代2次的迭代器
'编译前'function *gen() { yield 1; yield 2;}
其中的2次yield,在编译后果里,就会被拆成2个case, case 0 和 case 2
'编译后'function gen() { return regeneratorRuntime.wrap(function gen$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return 1; case 2: _context.next = 4; return 2; case 4: case "end": return _context.stop(); } } }, _marked);}
那如果迭代器外面有for循环呢,是怎么解决的?
'编译前'function *gen() { for (let i = 0; i < len; i++) { before = 'yield前语句' yield i; after = 'yield后语句' }}
for循环中,每一次循环会被拆成多个case
function gen() { var i; return regeneratorRuntime.wrap(function gen$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: i = 0; case 1: if (!(i < len)) { _context.next = 9; break; } before = 'yield前语句'; _context.next = 5; return i; case 5: after = 'yield后语句'; case 6: i++; _context.next = 1; break; case 9: case "end": return _context.stop(); } } }, _marked);}
for 循环会被拆成几局部,首先for(...)括号里的三局部会被拆成三个局部
- initialization - 初始化语句 对应 case 0
- condition - 条件 对应 case 1 中结尾局部
- final expression - 最终表达式 对应 case 6
for循环外部逻辑:
- case 1 和 case 5 别离示意for的大括号外面,yield前和yield后代码。
参考:
ES6 系列之 Babel 将 Generator 编译成了什么样子
从协程到状态机--regenerator源码解析(一)