function*
(星号的英文是 asterisk) 用来定义一个 生成器函数。
该函数可以看做是一个批量制造 生成器 的工厂函数(but 不能 new
),所以构造生成器还需要再调用一次。
下面的例子定义了自然数生成器:
function* makeNatureGenerator(n) {
let i = 0;
while (i <= n) {
yield i++;
}
}
const g5 = makeNatureGenerator(5)
console.log([...g5]) // [0, 1, 2, 3, 4, 5]
yield & next
生成器函数 是一种特殊的函数,它可以在执行到 yield
语句时暂时暂停,然后退出去执行其它代码,然后在指定的时刻又可以恢复执行。
在暂停退出到再次恢复执行的这个时间内,其执行上下文会被保存下来。
调用生成器函数(如上文的 makeNatureGenerator
)并不会立即执行函数体的代码,而是返回一个迭代器(如上文的 g5
)。
PS:这里我们说调用生成器函数会返回一个迭代器,因为生成器也是一种迭代器。
那么什么时候开始执行函数体代码呢?在调用生成器的 next()
方法时:
- 如果是第一次调用
next()
方法,会从函数体的第一行代码开始执行,直到- 遇到第一个
yield
暂停执行并返回值 - 遇到
return
结束迭代并返回值(如果有的话) - 抛出未捕获的异常并结束迭代
- 遇到第一个
- 如果不是第一次调用
next()
方法,会从上次暂停的地方继续开始执行,直到遇到下一个yield
、return
或者抛出未捕获的异常
带参的 next
调用生成器的 next()
方法时是可以携带一个数据的,该数据会作为待恢复的 yield
表达式的值,此时将 yield
表达式作为右值,可以用一个变量接收 next()
方法传入的数据,例如
function* times(n) {
let i = 0;
let scale = 1;
while (i <= n) {
console.log(`i = ${i}, scale = ${scale}`)
scale = yield (i++) * scale;
}
}
const g = times(3)
上面的例子中,g
是一个长度为3的序列生成器,每次迭代时需要传入一个 scale
值,与索引值的乘积作为迭代返回值。
第一次迭代时:
console.log(g.next(3).value)
// i = 0, scale = 1
// 0
此时因为并不是从某个暂停的 yield
恢复执行的,所以传入的 3 似乎没有起作用。
返回的 0 实际上是 i (=0) * scale (=1) 的结果。
第二次迭代时:
console.log(g.next(5).value)
// i = 1, scale = 5
// 5
首先将 5 作为 yield
语句的值赋值给左边,进入下次循环时 scale = 5
,返回 i (=1) * scale (=5)。
📝 另外需要注意的是,如果执行代码的过程中遇到 return
或者抛出未捕获的异常,将立即结束迭代并返回。结束迭代意味着再使用 next()
将直接返回 { done: true, value: undefined }
。
yield*
yield
用来返回一个数据,可以是基本类型也可以是引用类型,而 yield*
用来返回另一个生成器函数。
一个例子:
function* anotherGenerator(i) {
yield i * 2;
yield i *3;
}
function* generator(i) {
yield i;
yield* anotherGenerator(i);
yield i + 1;
}
const g = generator(5)
下面是迭代结果及解析:
// 执行到 line 7 返回 7
g.next().value //=> 5
// 执行到 line 8 进入另一个生成器执行
// 执行到 line 2 返回 5 * 2 = 10
g.next().value //=> 10
// 继续迭代另一个生成器,执行到 line 3 返回 15
g.next().value //=> 15
// 另一个生成器迭代完了,意味着 line 8 也执行完了
// 执行到 line 9,返回 6
g.next().value //=> 6
g.next().value //=> undefined
GeneratorFunction
GeneratorFunction
是另外一种创建 生成器函数 的方式,在 JS 中每个生成器函数都是 GeneratorFunction
对象。
然而,GeneratorFunction
并不是一个全局类,但是我们可以间接获取它:
const GeneratorFunction = Object.getPrototypeOf(function*(){}).constructor
const f = function*(){}
console.log(f instanceof GeneratorFunction) //=> true
一般都用 function*
定义生成器函数。
其它
📝 生成器函数还可以声明为属性:
const generators = {
*generator1() { yield 1; },
*generator2() { yield 2; }
}
const g1 = generators.generator1()
const g2 = generators.generator2()
console.log([...g1, ...g2]) //=> [1, 2]
📝 生成器函数是不能 new
的,虽然生成器函数的行为有点像构造函数(批量制造生成器)。
📝 可以用函数表达式定义:const generator = function* () {yield 1;}
Reference
👉 MDN: function*, GeneratorFunction, yield, yield*
评论区