生成器
生成器可以在函数内暂停和恢复执行。
生成器基础
生成器的形式是函数,在函数名前面加“*”,表示它是一个生成器。所有可以定义函数的地方都可以定义生成器,除了箭头函数。
标识生成器函数的星号不受空格限制。
1 | // 生成器函数声明 |
调用生成器函数会产生一个生成器对象。生成器对象一开始是暂停(Suspend)状态。与迭代器相似,生成器也实现了Iterator接口,因此具有next()方法。调用这个方法会让生成器开始或恢复执行。
1 | function * generatorFn() {} |
next()方法的返回值类似迭代器,有一个done属性和value属性。函数体为空的生成器函数中间不会停留。调用next()就会让生成器到达done: true状态。
value属性是生成器函数的返回值,默认undefiend,可以通过生成器函数的返回值指定:
1 | function * generatorFn() { |
生成器实现了Iterator接口,它们默认的迭代器是自引用的。
1 | g === g[Symbol.iterator]() // true |
通过yield中断执行
yield关键字可以让生成器停止和开始执行。
生成器函数在遇到yield关键字之前正常执行,遇到这个关键字后,停止执行,函数作用域的状态会被保留,停止执行的生成器函数只能调用生成器对象的next()方法来恢复执行。
1 | function * generatorFn() { |
yield有点像函数的中间返回语句,它生成的值会作为next()方法的返回对象。通过yield关键字退出的生成器函数处于done: false状态,通过return退出的生成器函数会处于done: true状态。
1 | function * generatorFn() { |
生成器函数内部的执行流程会针对每个生成器对象区分作用域,在一个生成器对象上调用next()不会影响其他生成器:
1 | function * generatorFn() { |
yield关键字只能在生成器函数内部使用,用在其他地方会抛出错误。
yield关键字必须直接位于生成器函数定义中,出现在嵌套的生成器函数会抛出错误。
1.生成器函数作为可迭代对象
显式调用生成器对象的next()方法的作用不大,把生成器对象当成可迭代对象会更加方便:
1 | function * generatorFn() { |
2.使用yield实现输入输出
第一次调用next()方法传入的值不会被使用,因为这一次调用为了开始执行生成器函数:
1 | function * generatorFn(initial) { |
yield关键字可同时用于输入和输出:
1 | function * generatorFn() { |
3.产生可迭代对象
可以使用星号增强yield的行为,让它能够迭代一个可迭代对象。
1 | function * generatorFn() { |
与生成器函数类似,yield关键字星号不受两侧空格影响:
1 | function * generatorFn1() { |
4.使用yield实现递归
1 | function * nTimes(n) { |
生成器作为默认迭代器
1 | class Foo { |
提前终止生成器
一个实现Iterator接口的对象一定有next()方法和一个可选的return()方法用于提前终止迭代器。
生成器对象除了有next,return方法还有throw()方法。
1 | function * generatorFn() {} |
return和throw都可以强制生成器进入关闭状态。
1.return()
提供给return的值就是终止迭代器对象的值:
1 | function * generatorFn() { |
与迭代器不同,所有生成器对象都有return方法,只要通过它进入关闭状态就无法恢复了,后续调用next()会显示done: true状态,而提供的任何值都不会被存储。
1 | function * generatorFn() { |
2.throw()
throw方法会在暂停的时候提供一个错误注入到生成器对象中,如果错误未被处理,生成器就会关闭:
1 | function * generatorFn() { |
如果在生成器函数内部处理了这个错误,生成器就不会被关闭,而且还可以恢复执行,错误处理会跳过yield,因此在这个例子中会跳过一个值:
1 | function * generatorFn() { |
如果生成器函数还没有开始执行,那么调用throw抛出的错误不会在函数内部被捕获,因为这相当于在函数块外部抛出了错误。
小结
迭代器是一个可以由任意对象实现的接口,支持连续获取对象产出的每一个值。任何实现 Iterable 接口的对象都有一个 Symbol.iterator 属性,这个属性引用默认迭代器。默认迭代器就像一个迭代器 工厂,也就是一个函数,调用之后会产生一个实现 Iterator 接口的对象。
迭代器必须通过连续调用 next()方法才能连续取得值,这个方法返回一个 IteratorObject。这 个对象包含一个 done 属性和一个 value 属性。前者是一个布尔值,表示是否还有更多值可以访问;后 者包含迭代器返回的当前值。这个接口可以通过手动反复调用 next()方法来消费,也可以通过原生消 费者,比如 for-of 循环来自动消费。
生成器是一种特殊的函数,调用之后会返回一个生成器对象。生成器对象实现了 Iterable 接口, 因此可用在任何消费可迭代对象的地方。生成器的独特之处在于支持 yield 关键字,这个关键字能够 暂停执行生成器函数。使用 yield 关键字还可以通过 next()方法接收输入和产生输出。在加上星号之 后,yield 关键字可以将跟在它后面的可迭代对象序列化为一连串值。