本章将详细介绍 Babel 对 async / generator 的转译。
# 12.1 前置知识
# 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 }
2
3
4
5
6
7
8
9
10
# 迭代协议
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 }
}
2
3
内置可迭代协议的对象
目前所有的内置可迭代对象如下:
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 }
1
2
3
4
5
6
7
8
9另外,展开语法(
...
)内部也实现了可迭代协议:[...someString] // ["h", "i"]
1自定义可迭代对象
对象也可以通过提供自己的
@@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"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable]; // [1, 2, 3]
1
2
3
4
5
6
7接受可迭代对象的 API
很多 API 接受可迭代对象作为参数,例如:
new Map([iterable]) new WeakMap([iterable]) new Set([iterable]) new WeakSet([iterable]) Promise.all(iterable) Promise.race(iterable) Array.from(iterable)
1
2
3
4
5
6
7需要可迭代对象的语法
一些语句和表达式需要可迭代对象,比如
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"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 迭代器协议
什么是迭代器协议
在 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19无穷迭代器
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' // ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18使用生成器
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' // ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27ES2015 类 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' }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Generator 对象到底是一个迭代器,还是一个可迭代对象?
回顾下,迭代器指的是实现了 [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 的一个属性
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 12.2 Babel 对 generator 函数的转译
# 转译过程概述
并不是所有运行环境都支持 generator 语法的,对于这些环境,Babel 可以将其转译为 ES5 版本,其中,babel-preset-env 和 babel-plugin-transform-runtime 均与 generator 函数转译相关。
例如,待转译的源码为:
function* gen() {
yield 'a';
yield 'b';
}
2
3
4
在不同的 Babel 配置下,有不同的转译结果。
只配置 babel-preset-env
Babel 转译配置如下:
{ "presets": [ [ "@babel/preset-env" ] ] }
1
2
3
4
5源码会被转译为:
"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); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23转译结果依赖了全局变量
regeneratorRuntime
。新增 babel-plugin-transform-runtime
如果 Babel 的转译配置增加
babel-plugin-transform-runtime
插件:{ "plugins": [ [ "@babel/plugin-transform-runtime" ] ], "presets": [ [ "@babel/preset-env" ] ] }
1
2
3
4
5
6
7
8源码会被转译为:
"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); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27转译结果提供了
_regenerator
对象,避免依赖全局的regeneratorRuntime
。
# regenerator-runtime
资料: https://segmentfault.com/a/1190000023701257
对于源码:
function* gen() {
yield 'a';
yield 'b';
}
2
3
4
在只配置 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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
单独看转译结果,有 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
2
3
4
5
6
7
8
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();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
其运行图如下: