前言
本篇主要记录一些生成器的语法细节和使用场景
语法细节
生成器对象作为可迭代对象
直接调用生成器对象的next()方法用处不大,可以直接将生成器对象作为可迭代对象去使用
function *genFn() {
yield 1;
yield 2;
yield 3;
return 4;
}
const gen = genFn()
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.next()) // {value: 2, done: false}
console.log(gen.next()) // {value: 3, done: false}
console.log(gen.next()) // {value: 4, done: true}
for (const i of genFn()) {
console.log(i) // 1, 2, 3
}
console.log([...genFn()]) // [1, 2, 3]
上面的生成器函数genFn返回的生成器对象可以直接被原生结构for-of以及...运算符迭代,但是使用时有几个值得注意的点
- 生成器函数内部的执行流程会针对每个生成器对象区分作用域,也就是每个生成器对象之间不会相互影响
- yield关键字只能在生成器函数内部使用
- 生成器函数的返回值不会被迭代,只会作为最后一次执行是返回迭代结果对象的value
使用yield实现输入和输出
上一次让函数暂停的yield关键字会接收到下一次调用next()传递的第一个参数值,但是传递给第一次调用next()的值不会被接收,因为第一次是让生成器函数开始执行,但是第一个yield产出的值会做为第一个next()返回的迭代器结果对象的值
function* genFn(x) {
console.log(x) // x
console.log(yield 1) // b
console.log(yield 1) // c
console.log(yield 1) // d
return 4
}
const gen = genFn('x')
console.log(gen.next('a')) // {value: 1, done: false}
console.log(gen.next('b')) // {value: 2, done: false}
console.log(gen.next('c')) // {value: 3, done: false}
console.log(gen.next('d')) // {value: 4, done: true}
可以看到第一次调用next()传递的值a
不会被接收到
产生可迭代对象
可以通过*号增强yield关键字,让它去迭代一个可迭代对象,从而一次产生一个值
function* genFn(x) {
yield* [1, 2, 3]
yield* [4, 5, 6]
}
console.log([...genFn()]) // [1, 2, 3, 4, 5, 6]
yield其实就是把可迭代对象的数值转化为一连串可以单独产出的值,yield接收到的值是迭代器返回 done:true 时的value,对于普通迭代器那么这个值就是undefined,如果是生成器那么这个值就是这个生成器函数的返回值
function* genFoo() {
yield 'a'
return 'b'
}
function* genFn(x) {
console.log('迭代器yield的值:', yield* [1, 2, 3]) // undefined
console.log('生成器yield的值:', yield* genFoo()) // 'b'
}
console.log([...genFn()]) // [1, 2, 3, 'a']
提前终止生成器
生成器和迭代器一样也有关闭
这个概念,生成器对象上原生实现了可选的return()和throw()方法,调用后可强制生成器进入关闭状态
- return() 我们可以主动调用return()方法让生成机进入关闭状态,而且关闭之后就无法恢复了,继续调用next()方法只会显示done:true的状态
在使用for-of等内置语言结构时,关闭生成器时的值也就是迭代器返回结果对象{done:true, value: 'xxx'}会被忽略
function* genFn() {
yield* [1, 2, 3, 4, 5]
}
const gen = genFn()
console.log(gen) // genFn {<suspended>}
console.log(gen.return(8)) // {value: 8, done: true}
console.log(gen) // genFn {<closed>}
console.log(gen.next()) // {value: undefined, done: true}
const genFoo = genFn()
for (const i of genFoo) {
console.log(i) // 1, 2, 3
if(i > 2) {
console.log(genFoo.return(8)) // {value: 8, done: true}
}
}
- throw() throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中,如果错误没有被处理,生成器就会关闭
function* genFn() {
yield* [1, 2, 3, 4, 5]
}
const gen = genFn()
console.log(gen) // genFn {<suspended>}
try {
gen.throw('error')
} catch (error) {
console.log(gen) // genFn {<closed>}
}
不过如果我们在生成器内部处理了这个错误,那么生成器就不会关闭,而且还可以继续执行,错误处理会跳过对应的yield,所以会少一个输出值
function* genFn() {
for (const i of [1, 2, 3, 4, 5]) {
try {
yield i
} catch (e) {
console.log(e)
}
}
}
const gen = genFn()
console.log(gen) // genFn {<suspended>}
for (const i of gen) {
console.log(i) // 产出了 2 之后抛出错误,3不会被产出,所以输出结果为 1, 2, 'error', 4, 5
if (i === 2) {
gen.throw('error')
}
}
console.log(gen) // genFn {<closed>}
使用场景
生成器作为默认迭代器
在实例的原型上面实现Symbol.iterator,并且定义为生成器函数,那么返回的生成器对象可以作为迭代器使用
class Foo {
constructor() {
this.value = [1, 2, 3]
}
*[Symbol.iterator]() {
yield* this.value
}
}
for (const i of new Foo()) {
console.log(i) // 1, 2, 3
}
使用yield*实现递归
function *nTimes(n) {
if(n > 0) {
yield* nTimes(n - 1)
yield n - 1
}
}
for (const i of nTimes(10)) {
console.log(i)
}
总结
本文主要介绍了生成器的一些语法细节和一些使用案例,如果想要对生成器有更加深入,更加系统的了解,可以看看文末推荐的参考文献,后续也会出一篇生成器深入的博客
参考文献
- JavaScript高级程序设计
- 你不知道的js(下)
- JavaScript忍者秘籍
- es6.ruanyifeng.com/#docs/gener…
- developer.mozilla.org/zh-CN/docs/…