• 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}
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    [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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    一些原本就可以遍历的对象(比如数组对象)存在自己原来默认的[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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    因为在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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    function* generator() {
        for (let i = 1; i <= 3; i++) yield i
    }
    
    console.log(...generator()) // 1 2 3
    
    • 1
    • 2
    • 3
    • 4
    • 5

    把[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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    另外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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    异步迭代

    如果我们要在生成器内部执行异步内容,然后再进行异步迭代,可以使用异步生成器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
        }
    })()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    尾言

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

  • 相关阅读:
    自媒体人必备的热点舆情分析,3个网站
    英文科技论文写作与发表-格式和规范(第5章)
    【网络研究院】攻击者扫描 160 万个 WordPress 网站以查找易受攻击的插件
    Cisco简单配置(十八)—OSPF
    面向对象综合练习(上)
    Python1-Pillow库简单使用
    Win11通过注册表或者kernel32.dll的SetUserGeoName等方式设置国家或地区后重启过一会就自动变回原来的值...
    07Thymeleaf
    什么是分库分表-03
    2.1-梯度下降
  • 原文地址:https://blog.csdn.net/weixin_43877799/article/details/126764794