生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。
平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。
生成器函数也是一个函数,但是和普通的函数有一些区别:
首先,生成器函数需要在function的后面加一个符号:*
其次,生成器函数可以通过yield关键字来控制函数的执行流程
最后,生成器函数的返回值是一个Generator(生成器)
生成器事实上是一种特殊的迭代器
生成器是一个函数的形式,通过在函数名称前加一个星号(*)就表示它是一个生成器。所以只要是可以定义函数的地方,就可以定义生成器
function* gFn() { }
const gFn = function* () { }
const o = {
* gFn() { }
}
箭头函数不能用来定义生成器函数,因为生成器函数使用( function*)语法编写。
那为啥上面的第三个例子可以不使用 (function*)语法呢?
因为那个是简写版本。等价于:
const o = {
gFn: function* () { }
}
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())
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状态,并停止
我们很多时候不希望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())
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')
然后,可能就会有人问,第一次传的 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))
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())
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,即会跳过一个值。
我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:
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())
事实上我们还可以使用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())
结果同上!