侧边栏壁纸
博主头像
我的学习心得 博主等级

行动起来,活在当下

  • 累计撰写 223 篇文章
  • 累计创建 60 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

生成器函数

Administrator
2022-04-04 / 0 评论 / 0 点赞 / 1311 阅读 / 0 字

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() 方法,会从上次暂停的地方继续开始执行,直到遇到下一个 yieldreturn 或者抛出未捕获的异常

带参的 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*

👉 我的笔记:生成器, 迭代器

0

评论区