js 谈谈Generator和[Symbol.iterator]

前言

对象中的[Symbol.iterator]属性,一般用来作为对象默认的迭代方法,当它返回迭代器对象时,可以帮助我们创建对象的可迭代方法。

Generator是es6引入的一种特殊函数,它叫做生成器函数,调用它可以得到一个迭代器对象。

我们从上文可以发现,一个需要返回迭代器对象,一个可以得到迭代器对象,必然可以合作,这就是有趣的地方。

今天我们就来谈谈它们是什么,怎么用。

迭代器对象


满足两点:

  1. 包含next函数。
  2. next函数返回一个包含valuedone属性的对象。(doneBoolean类型)
const inter = {
    x: 1,
    next() {
        return this.x <= 3 ? {value: this.x++} : {done: true}
    }
}

[Symbol.iterator]


[Symbol.iterator]是什么呢?它是一个对象的方法,这个方法很特殊,是默认的对象迭代方法。

当它内部返回一个迭代器对象时,该对象就可以迭代了

每当我们使用for...of或者拓展操作符...遍历该对象时,进行的就是迭代操作

我们用上文的迭代器当例子。

const inter = {
    x: 1,
    next() {
        return this.x <= 3 ? {value: this.x++} : {done: true}
    }
}
const test = {
    [Symbol.iterator]() {
        return inter
    }
}
console.log(...test) // 1 2 3

// 没有[Symbol.iterator]方法的test2,不可以迭代
const test2 = {}
console.log(...test2) // 报错: Found non-callable @@iterator

一些原本就可以遍历的对象(比如数组对象)存在自己原来默认的[Symbol.iterator]方法(所以平时我们默认就可以对它们进行迭代操作),当然,你可以进行重写该方法修改迭代的结果。

一些不存在该方法的对象默认是不能够用for...of或者拓展操作符...遍历它的,你可以通过自己主动在对象上添加[Symbol.iterator]方法达到让该对象可以进行遍历的目的。


另外,为了方便起见,我们也会让迭代器对象加上[Symbol.iterator]函数返回自己(因为自己本身就是迭代器对象),让迭代器对象本身变得可以迭代。

const test = {
    [Symbol.iterator]() {
        return {
            x: 1,
            next() {
                return this.x <= 3 ? {value: this.x++} : {done: true}
            },
            [Symbol.iterator]() {
                return this
            }
        }
    }
}
console.log(...test) // 1 2 3
console.log(...test[Symbol.iterator]()) // 1 2 3

Generator

Generator是es6的知识,它叫做生成器函数,只要在函数声明使用function时改成function*符号,一个函数就成为了Generator函数。

而且正因为必须使用function*,所以箭头函数不可以作为Generator函数

function* generator() {
    yield 1
    yield 2
    yield 3
}

const gen = generator()

console.log(gen) // Object [Generator] {}

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: undefined, done: true }

我们调用生成器可以得到一个迭代器对象,怎么证明?

因为在Generator函数中,我们可以用yield声明迭代节点,函数会返回一个Generator对象Object [Generator] {},这个对象上存在方法next,调用next可以不停迭代节点对象出来。

节点对象中包含了value就是迭代节点的值,done表示迭代是否结束。

以上完全符合迭代器对象的样子。


另外,要知道的是,Generator返回的迭代器对象本身实现了[Symbol.iterator]方法,那么我们自然可以遍历它,遍历它可以通过for...of或者拓展操作符...

function* generator() {
    yield 1
    yield 2
    yield 3
}

const gen1 = generator()
const gen2 = generator()

for (let i of gen1) {
    console.log(i) // 1 2 3
}

console.log(...gen2) // 1 2 3

Generator函数内我们也可以用一些遍历的方法输出迭代节点的值,看起来更美观。

function* generator() {
    for (let i = 1; i <= 3; i++) yield i
}

console.log(...generator()) // 1 2 3

把[Symbol.iterator]写成生成器函数

既然生成器的返回结果就是迭代器对象,那只要把[Symbol.iterator]方法写成生成器方法,不就不用手动返回迭代器对象了吗,方便了很多。

const test = {
    x: 3,
    * [Symbol.iterator]() {
        for (let i = 1; i <= this.x; i++) yield i
    }
}
console.log(...test)// 1 2 3

另外class声明的类对象,也可以在其中添加[Symbol.iterator]方法,达到让其实例对象拥有可遍历能力的目的。

class Fa {
    constructor(n) {
        this.n = n
    }

    * [Symbol.iterator]() {
        for (let i = 0; i < this.n; i++) yield i
    }
}

const fa = new Fa(10)

console.log(...fa) // 0 1 2 3 4 5 6 7 8 9

异步迭代

如果我们要在生成器内部执行异步内容,然后再进行异步迭代,可以使用异步生成器async function*,然后使用for await异步迭代。

async function* clock() {
    for (let i = 0; i < 3; i++) {
        // 模拟异步任务,1秒后才能得到新的迭代数据
        let count = await new Promise((resolve, reject) => setTimeout(() => {
            resolve(i)
        }, 1000))
        yield count
    }
}

(async function list() {
    // for await 也要再在异步函数中执行
    for await (let i of clock()) {
        console.log(i) // 0..一秒后..1..一秒后..2
    }
})()

尾言

如果有任何错误或者建议,欢迎指出,我会及时修改,如果文章对你有帮助的话,欢迎点赞收藏,感谢~

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在下月亮有何贵干

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值