Description
Deep In Babel
本章将详细介绍 Babel 对 async / generator 的转译。
Generator 指的是生成器对象,该对象由 generator 函数返回,Generator 对象符合迭代协议。
function* gen() { // generator 函数
yield 'a';
yield 'b';
}
let g = gen(); // "Generaotr 对象 {}"
console.log(g.next()); // { value: "a", done: false }
console.log(g.next()); // { value: "b", done: false }
console.log(g.next()); // { value: undefined, done: true }
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#iterable
作为 ECMAScript 2015 的一组补充规范,迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。
迭代协议具体分为两个协议: 可迭代协议和迭代器协议。
可迭代协议允许 JavaScript 对象设置自身的迭代行为。比如在 for...of
结构中,对象的哪些值可以被遍历到。
要成为可迭代对象,一个对象必须实现 @@iterator
方法。这意味着对象(或者其原型链上的某个对象)必须有一个键为 @@iterator
的属性,可通过常量 Symbol.iterator
访问该属性,示例:
const obj = {
[Symbol.iterator]: function() { return this }
}
内置可迭代协议的对象
目前所有的内置可迭代对象如下: String
、Array
、TypedArray
、Map
、Set
,它们的原型对象都实现了 @@iterator
方法。
比如 String
关于 @@iterator
方法的示例:
const someString = "hi";
typeof someString[Symbol.iterator]; // "function"
const iterator = someString[Symbol.iterator]();
iterator + ""; // "[object String Iterator]"
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
另外,展开语法(...
)内部也实现了可迭代协议:
[...someString] // ["h", "i"]
自定义可迭代对象
对象也可以通过提供自己的 @@iterator
方法,重新定义迭代行为:
// 必须构造 String 对象以避免字符串字面量 auto-boxing
const someString = new String("hi");
someString[Symbol.iterator] = function() {
return { // 只返回一次元素,字符串 "bye",的迭代器对象
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
[...someString]; // ["bye"]
someString + ""; // "hi"
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable]; // [1, 2, 3]
接受可迭代对象的 API
很多 API 接受可迭代对象作为参数,例如:
new Map([iterable])
new WeakMap([iterable])
new Set([iterable])
new WeakSet([iterable])
Promise.all(iterable)
Promise.race(iterable)
Array.from(iterable)
需要可迭代对象的语法
一些语句和表达式需要可迭代对象,比如 for...of
循环、展开语法、yield*
,和解构赋值。
for(let value of ["a", "b", "c"]){
console.log(value);
}
// "a"
// "b"
// "c"
[..."abc"]; // ["a", "b", "c"]
function* gen() {
yield* ["a", "b", "c"];
}
gen().next(); // { value: "a", done: false }
[a, b, c] = new Set(["a", "b", "c"]);
a // "a"
什么是迭代器协议
在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在序列终止时可能返回一个返回值。
迭代器协议描述了一个迭代器需要长什么样子,或者说需要有如下功能:
迭代器对象需实现了一个 next()
方法
next()
方法必须返回一个对象,该对象应当有两个属性: done
和 value
done
和 value
需要满足如下语义:
done: boolean
如果迭代器可以产生序列中的下一个值,则为 false
。
如果迭代器已将序列迭代完毕,则为 true
。这种情况下,value
是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
value: any
迭代器返回的任何 JavaScript 值。done
为 true
时可省略。
迭代器示例
简单迭代器
function makeIterator(array) {
let nextIndex = 0;
return {
next: function () {
return nextIndex < array.length ? {
value: array[nextIndex++],
done: false
} : {
done: true
};
}
};
}
let it = makeIterator(['哟', '呀']);
console.log(it.next().value); // '哟'
console.log(it.next().value); // '呀'
console.log(it.next().done); // true
无穷迭代器
function idMaker() {
let index = 0;
return {
next: function() {
return {
value: index++,
done: false
};
}
};
}
let it = idMaker();
console.log(it.next().value); // '0'
console.log(it.next().value); // '1'
console.log(it.next().value); // '2'
// ...
使用生成器
function* makeSimpleGenerator(array) {
let nextIndex = 0;
while(nextIndex < array.length) {
yield array[nextIndex++];
}
}
let gen = makeSimpleGenerator(['哟', '呀']);
console.log(gen.next().value); // '哟'
console.log(gen.next().value); // '呀'
console.log(gen.next().done); // true
function* idMaker() {
let index = 0;
while (true) {
yield index++;
}
}
let gen = idMaker();
console.log(gen.next().value); // '0'
console.log(gen.next().value); // '1'
console.log(gen.next().value); // '2'
// ...
ES2015 类 class 中的迭代器
class SimpleClass {
constructor(data) {
this.data = data
}
[Symbol.iterator]() {
// Use a new index for each iterator. This makes multiple
// iterations over the iterable safe for non-trivial cases,
// such as use of break or nested looping over the same iterable.
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return {value: this.data[index++], done: false}
} else {
return {done: true}
}
}
}
}
}
const simple = new SimpleClass([1,2,3,4,5])
for (const val of simple) {
console.log(val) //'1' '2' '3' '4' '5'
}
回顾下,迭代器指的是实现了 [Symbole.iterator]
属性的对象,可迭代对象是实现了 next()
方法且其返回值是符合规范的 { done, value }
类型的对象。
生成器对象既是迭代器,也是可迭代对象:
let aGeneratorObject = function* (){
yield 1;
yield 2;
yield 3;
}();
typeof aGeneratorObject.next;
// 返回 "function", 因为有一个 next 方法,所以这是一个迭代器
typeof aGeneratorObject[Symbol.iterator];
// 返回 "function", 因为有一个 @@iterator 方法,所以这是一个可迭代对象
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// 返回 true, 因为 @@iterator 方法返回自身(即迭代器),所以这是一个格式良好的可迭代对象
[...aGeneratorObject];
// 返回 [1, 2, 3]
console.log(Symbol.iterator in aGeneratorObject)
// 返回 true, 因为 @@iterator 方法是 aGeneratorObject 的一个属性
并不是所有运行环境都支持 generator 语法的,对于这些环境,Babel 可以将其转译为 ES5 版本,其中,babel-preset-env 和 babel-plugin-transform-runtime 均与 generator 函数转译相关。
例如,待转译的源码为:
function* gen() {
yield 'a';
yield 'b';
}
在不同的 Babel 配置下,有不同的转译结果。
只配置 babel-preset-env
Babel 转译配置如下:
{
"presets": [
[ "@babel/preset-env" ]
]
}
源码会被转译为:
"use strict";
var _marked = /*#__PURE__*/regeneratorRuntime.mark(gen);
function gen() {
return regeneratorRuntime.wrap(function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'a';
case 2:
_context.next = 4;
return 'b';
case 4:
case "end":
return _context.stop();
}
}
}, _marked);
}
转译结果依赖了全局变量 regeneratorRuntime
。
新增 babel-plugin-transform-runtime
如果 Babel 的转译配置增加 babel-plugin-transform-runtime
插件:
{
"plugins": [
[ "@babel/plugin-transform-runtime" ]
],
"presets": [
[ "@babel/preset-env" ]
]
}
源码会被转译为:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _marked = /*#__PURE__*/_regenerator.default.mark(gen);
function gen() {
return _regenerator.default.wrap(function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'a';
case 2:
_context.next = 4;
return 'b';
case 4:
case "end":
return _context.stop();
}
}
}, _marked);
}
转译结果提供了 _regenerator
对象,避免依赖全局的 regeneratorRuntime
。
资料: https://segmentfault.com/a/1190000023701257
对于源码:
function* gen() {
yield 'a';
yield 'b';
}
在只配置 babel-preset-env
时,转译结果是:
"use strict";
var _marked = /*#__PURE__*/regeneratorRuntime.mark(gen);
function gen() {
return regeneratorRuntime.wrap(function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'a';
case 2:
_context.next = 4;
return 'b';
case 4:
case "end":
return _context.stop();
}
}
}, _marked);
}
单独看转译结果,有 2 个问题:
regeneratorRuntime
regeneratorRuntime.wrap
、regeneratorRuntime.mark
都是什么意思首先,需确认下,转译后代码中的 regeneratorRuntime
来自 babel-runtime
包,babel-runtime
包又依赖了 regenerator-runtime
,即依赖关系是:
babel-preset-env -> babel-runtime -> regenerator-runtime
regenerator-runtime
工具包是 Facebook 公司推出的用 ES5 写法运行 ES6 的 generator 函数的辅助工具,其目录主要结构为:
regenerator
├─ README.md
├─ bin
├─ main.js
├─ packages
│ ├─ preset
│ ├─ runtime
│ └─ transform
regeneratorRuntime
定义在文件 regenerator/packages/runtime/runtime.js
,具体的,regeneratorRuntime
有如下功能:
regeneratorRuntime
挂载到全局regeneratorRuntime.mark
regeneratorRuntime.wrap
源码有 750 行左右,通读该源码可以了解 regeneratorRuntime
的作用。
笔者整理了一份精简版的 regeneratorRuntime
定义,可简要地介绍其核心逻辑。
(function () {
var window = self = global;
var ContinueSentinel = {};
var mark = function (genFun) {
var generator = Object.create({
next: function (arg) {
return this._invoke("next", arg);
}
});
genFun.prototype = generator;
return genFun;
};
function wrap(innerFn, outerFn, self) {
var generator = Object.create(outerFn.prototype);
var context = {
done: false,
method: "next",
next: 0,
prev: 0,
abrupt: function (type, arg) {
var record = {};
record.type = type;
record.arg = arg;
return this.complete(record);
},
complete: function (record, afterLoc) {
if (record.type === "return") {
this.rval = this.arg = record.arg;
this.method = "return";
this.next = "end";
}
return ContinueSentinel;
},
stop: function () {
this.done = true;
return this.rval;
}
};
generator._invoke = makeInvokeMethod(innerFn, context);
return generator;
}
function makeInvokeMethod(innerFn, context) {
var state = "start";
return function invoke(method, arg) {
if (state === "completed") {
return { value: undefined, done: true };
}
context.method = method;
context.arg = arg;
while (true) {
state = "executing";
var record = {
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);
function gen() {
return regeneratorRuntime.wrap(
function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'a';
case 2:
_context.next = 4;
return 'b';
case 4:
case "end":
return _context.stop();
}
}
},
_marked,
this
);
}
// 运行 gen
var g = gen();
g.next();
g.next();
g.next();
其运行图如下:
This app can be installed on your PC or mobile device. This will allow this web app to look and behave like any other installed app. You will find it in your app lists and be able to pin it to your home screen, start menus or task bars. This installed web app will also be able to safely interact with other apps and your operating system.
Deep In Babel