JS生成器的介绍

1、 什么是生成器

生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。

平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。
生成器函数也是一个函数,但是和普通的函数有一些区别:

首先,生成器函数需要在function的后面加一个符号:*
其次,生成器函数可以通过yield关键字来控制函数的执行流程
最后,生成器函数的返回值是一个Generator(生成器)
生成器事实上是一种特殊的迭代器

2、 生成器的声明

生成器是一个函数的形式,通过在函数名称前加一个星号(*)就表示它是一个生成器。所以只要是可以定义函数的地方,就可以定义生成器

function* gFn() { }

const gFn = function* () { }

const o = {
    * gFn() { }
}

箭头函数不能用来定义生成器函数,因为生成器函数使用( function*)语法编写。
那为啥上面的第三个例子可以不使用 (function*)语法呢?
因为那个是简写版本。等价于:

const o = {
    gFn: function* () { }
}

3、 生成器的使用

3.1 生成器函数执行

function* gFn() {
    console.log(111)
}
gFn()

然后,我们会很"开心"地发现控制台没有打印任何信息。
这是因为调用生成器函数会产生一个生成器对象,但是这个生成器一开始处于暂停执行的状态,需要调用 next方法才能让生成器开始或恢复执行。

return会直接让生成器到达 done: true状态:

function* gFn() {
    console.log(111)
    return 222
}
const g = gFn()
console.log("1",g)
console.log("2",g.next())
console.log("3",g.next())

JS生成器的介绍_第1张图片
生成器代码的执行可以被yield控制:

function* gFn() {
     yield 
     console.log(111)
     return 222
}
const g = gFn()
console.log("1",g)
console.log("2",g.next())
console.log("3",g.next())

yield关键字
提到生成器,自然不能忘记 yield关键字。 yield能让生成器停止,此时函数作用域的状态会被保留,只能通过在生成器对象上调用 next方法来恢复执行。
上面我们已经说了, return会直接让生成器到达 done: true状态,而 yield则是让生成器到达 done: false状态,并停止

JS生成器的介绍_第2张图片

我们很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果:

function* gFn() {
     yield 100
     console.log(111)
     return 222
}
const g = gFn()
console.log("1",g)
console.log("2",g.next())
console.log("3",g.next())

JS生成器的介绍_第3张图片
3.2 生成器传递参数
函数既然可以暂停来分段执行,那么函数应该是可以传递参数的,我们是否可以给每个分段来传递参数呢?
答案是可以的, 我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值;

function* gFn(initial) {
  console.log(initial)
  console.log(yield)
  console.log(yield)
  console.log(yield)
}
const g = gFn('red')
g.next('white')
g.next('blue')
g.next('purple')

JS生成器的介绍_第4张图片

  1. 生成生成器,此时处于暂停执行的状态
  2. 调用 next,让生成器开始执行,输出 red,然后准备输出 yield,发现是 yield,暂停执行,出去外面一下。
  3. 外面给 next方法传参 blue,又恢复执行,然后之前暂停的地方(即 yield)就会接收到 blue。然后又遇到 yield暂停。
  4. 又恢复执行,输出 purple

然后,可能就会有人问,第一次传的 white怎么消失了?
它确实消失了,因为第一次调用 next方法是为了开始执行生成器函数,而刚开始执行生成器函数时并没有 yield接收参数,所以第一次调用 next的值并不会被使用。

yield关键字同时用于输入和输出: yield可以和 return同时使用,同时用于输入和输出

function* gFn() {
  yield 111
  return yield 222
}
const g = gFn()
console.log(g.next(333))
console.log(g.next(444))
console.log(g.next(555))

JS生成器的介绍_第5张图片

  1. 生成生成器,此时处于暂停执行的状态
  2. 调用 next,让生成器开始执行,遇到 yield,暂停执行,因为 yield后面还有111,所以带着111作为输出出去外面。
  3. 调用 next,生成器恢复执行,遇到 return,准备带着后面的数据跑路,结果发现后面是
    yield,所以又带着222,作为输出到外面。
  4. 调用 next,又又又恢复执行,不过这个时候return的内容是 yield表达式,所以 yield会作为输入接收555,然后再把它带到外面去输出。

yield表达式需要计算要产生的值,如果后面没有值,那就默认是 undefined。 return yield x的语法就是,遇到 yield,先计算出要产生的值 111,在暂停执行的时候作为输出带出去,然后调用 next方法时, yield又作为输入接收 next方法的第一个参数

3.3 生成器抛出异常
throw也可以提前终止生成器,且会抛出异常,需要捕获处理抛出的异常。

function* gFn() {
  yield 111
  yield 222
  yield 333
}

const g = gFn()
// g.throw(444)       // 如果异常没有被处理的话,会直接报错

try {
  g.throw(444)
} catch (e) {
  console.log(e)
}
console.log(g)
console.log(g.next())

JS生成器的介绍_第6张图片
不过,如果处理异常是在生成器内部的话,情况就不太一样了。

function* gFn() {
  for (const x of [1, 2, 3]) {
    try {
      yield x
    } catch (e) {
      console.log(e)
    }
  }
}
const g = gFn()
console.log(g.next())
g.throw(444)
console.log(g)
console.log(g.next())

如果是在生成器内部处理这个错误,那么生成器不会关闭,还可以恢复执行,只是会跳过对应的yield,即会跳过一个值。

4、 生成器替代迭代器

我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:

const names = ["aaa", "bbb", "ccc"]

function* gFn(arr) {
  for (let i = 0; i < arr.length; i++) {
    yield arr[i]
  }
}

const g = gFn(names )
console.log(g.next()) 
console.log(g.next())
console.log(g.next())
console.log(g.next())

JS生成器的介绍_第7张图片
事实上我们还可以使用yield*来生产一个可迭代对象:这个时候相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值;

const names = ["aaa", "bbb", "ccc"]

function* gFn(arr) {
    yield* arr
}

const g = gFn(names)
console.log(g.next()) 
console.log(g.next())
console.log(g.next()) 
console.log(g.next()) 

结果同上!

你可能感兴趣的:(JavaScript,javascript,前端,开发语言)