js中的生成器

237 阅读4分钟

生成器

在函数名称前面加一个星号( * )表示这个函数是一个生成器。

注意: 箭头函数不能用来定义生成器函数

function* generator() {
    ...
} // 生成器函数

调用生成器函数会返回一个对象(通常叫做生成器对象)。注意:一开始生成器处于暂停执行的状态,此时生成器函数不会执行。

function* generator() {
    console.log('打印内容?')
}
const gObj = generator()
console.log(gObj) // 结果见 generator-1.png

生成器对象实现了Iterator接口,因此具有next()方法。调用gObjnext()会返回一个value属性和done属性的对象同时生成器函数执行。

实现接口可懂? 类似javaimplements关键字。上面重点就是具有next()方法

const obj = gObj.next()
console.log(obj) // 结果见 generator-2.png

生成器对象也实现了Iterable接口。它们默认的迭代器是自引用的

console.log(gObj === gObj[Symbol.iterator]()) // true

Iterable接口Iterator接口???

  • 实现了Iterable接口意味着该对象定义了Symbol.Iterator方法。且这个方法必须返回实现了Iterator接口的对象

  • 实现了Iterator接口意味着该对象定义了next()方法,且next()方法必须返回{done: Boolean, value: any}格式的对象

对于生成器对象来说,其本身就实现了next()方法,同时也实现了Symbol.iterator方法(返回的对象也定义了next()方法)

yield

yield可以让生成器对象调用next()方法来使生成器函数分阶段执行(保留上一次执行的状态)。

特性1:yield类似函数的中间返回语句(每个阶段的return)

返回的值会出现在生成器对象next()方法返回的对象里(value)

同时这个对象的done属性为false

通过return关键字退出的生成器函数会使生成器对象done属性为true

不管是yield还是return,返回的值都会成为next()返回的对象的value属性值

由于生成器对象实现了iterable接口,所以其是一个**可迭代对象。**这意味着可通过yield来实现循环:

function* generatorFunc() {
	yield 1
    yield 2
    yield 3
}
for(let item of generatorFunc()) {
    console.log(item)
} // 打印 1 2 3

特性2:yield关键字可以作为函数的中间参数使用。

什么意思呢?

就是指:上一次让生成器函数暂停的yield关键字会接收到传递给生成器对象的next()方法的第一个值**。也就是说 可以通过给生成器对象的next()方法传参来指定上一个yield表达式的值(注意了是整个表达式的值)

第一次调用next()时,传入的值不会被使用。因为第一次调用是为了开始执行生成器函数,它没有上一个yield了。

当遇到第一个yield时,函数暂停执行,此时,这个yield就成为了第二次调用next()的上一个yield

function* generatorFunc(initial) {
    console.log(initial)
    console.log(yield)
    console.log(yield)
}
const gObj = generatorFunc('initialValue')

gObj.next('1st next')
gObj.next('2st next')
gObj.next('3st next')
// 结果为 
// 'initialValue'
// '2st next'
// '3st next'

可以使用星号( * )来增强yield,让它能够迭代一个可迭代对象,从而一次产生(返回)一个值:

function* generatorFunc() {
    yield* [1, 2, 3]
}
// 它们是等价的
function* generatorFunc() {
    for(let item of [1, 2, 3]) {
        yield item
    }
}

需要注意的是:

yield* 表达式的值是关联迭代器返回done: true时的value属性。那么:

  • 对于普通迭代器来说,这个值就是undefined

  • 对于生成器函数产生的迭代器来说,这个值就是生成器函数的返回值。

因为生成器函数会返回一个实现了Iterator接口的对象。而这对于可迭代对象的迭代器来说是相同的。所以,对于其他对象定义迭代器时,可以将[Symbol.iterator]换成* [Symbol.iterator]。然后借助yield可以轻而易举实现迭代

const obj = {
    values: [1, 2, 3],
    * [Symbol.iterator]() {
        yield* this.values
    }
}
for(let item of obj) {
    console.log(item)
} // 打印 1 2 3

扩展:

生成器对象除了一个必须有的next()方法外,还支持可选的return()throw()

1、return()

调用return()方法会强制生成器进入关闭状态。也可以给return(arg)传递参数,参数的值会成为终止迭代器对象的值,也就是{ done: true, value: arg }

注意:只要通过return()进入关闭状态,就无法恢复了

for..of循环等内置语言结构会忽略状态done: true的对象的value的值

2、throw()

...

示例

    function * createIdMaker() {
        let i = 1
        while(true) {
            yield i++
        }
    }
    
    const idMaker = createIdMaker()
    
    console.log(idMaker.next().value) // 1
    console.log(idMaker.next().value) // 2
    console.log(idMaker.next().value) // 3