前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JS迭代器和生成器

JS迭代器和生成器

作者头像
kifuan
发布2022-10-24 16:34:32
1K0
发布2022-10-24 16:34:32
举报
文章被收录于专栏:随便写写-kifuan

源码

点击这里前往Github获取本文源码,相信你看文件名也知道对应哪一块,这些单词还是认识的。

背景

遍历数组的时候,我相信大多数人已经用上ES6的for...of语法了:

代码语言:javascript
复制
const arr = [1, 2, 3]

for (const item of arr) {
    console.log(item)
}
// 1 2 3

那么我们能不能让自己创建的对象也支持for...of呢? 答案自然是可以的,这么好用的语法不给一个自定义支持简直天理难容。

引入

在Python中,有一个好用的range对象,它可以让我们这样写代码:

代码语言:javascript
复制
for i in range(5):
    print(i)
# 0 1 2 3 4

print(list(range(5)))
# [0, 1, 2, 3, 4]

我们这里要借助迭代器和生成器实现的就是这个东西,实际上range在Python中也是通过迭代器的方式实现的。

同步迭代器

既然这么说了肯定有异步的迭代器,不过不着急,我们等会再讨论异步的问题,先上代码:

代码语言:javascript
复制
function range(end) {
    let start = 0

    const iterator = {
        next() {
            if (start === end) {
                return { done: true }
            }
            return { value: start++, done: false }
        }
    }

    return {
        [Symbol.iterator]: () => iterator
    }
}

for (const i of range(5)) {
    console.log(i)
}
// 0 1 2 3 4

我们定义了一个range函数,它返回一个可迭代对象

详细的内容请访问MDN查阅,我这里做一个总结:

  • 可迭代对象需要有一个Symbol.iterator函数属性,返回迭代器对象。
  • 迭代器对象需要有一个next函数属性,返回迭代结果。
  • 迭代结果需要有value属性和done属性,这个看名字就知道什么意思了。

所以说,根据这些,我们可以想到一个简化代码的思路,就是让range返回的对象本身也是一个迭代器,当调用Symbol.iterator时返回this

代码语言:javascript
复制
function range(end) {
    let start = 0
    return {
        next() {
            if (start === end) {
                return { done: true }
            }
            return { value: start++, done: false }
        },

        [Symbol.iterator]() {
            return this
        }
    }
}

for (const i of range(5)) {
    console.log(i)
}
// 0 1 2 3 4

已经优化了很多了,但是这样还是不够简练,于是引出我们下文要说的内容。

同步生成器

我们可以发现只是实现一个简单的数字范围生成器就需要10多行代码,这显然是有点麻烦的,于是es6为我们提供了一个东西,它就是生成器

代码语言:javascript
复制
function* range(end) {
    for (let i = 0; i < end; i++) {
        yield i
    }
}

for (const i of range(5)) {
    console.log(i)
}
// 0 1 2 3 4

现在代码简单多了,然后我们看一下range函数返回了什么东西:

代码语言:javascript
复制
const r = range(5)
console.log(r)
// Object [Generator] {}

const itor = r[Symbol.iterator]()
console.log(r === itor)
// true

和我们自己实现的时候类似,它调用Symbol.iterator时返回的是this

相关语法

下面给出与迭代器和生成器有关的语法。

for...of语法

遍历元素值:

代码语言:javascript
复制
const arr = [1, 2]

for (const i of arr) {
    console.log(i)
}
// 1 2

看到这里我们也可以大致模拟一下for...of内部的执行原理了:

代码语言:javascript
复制
function forOf(obj, action) {
    const itor = obj[Symbol.iterator]()

    while (true) {
        const res = itor.next()

        if (res.done) {
            break
        }

        action(res.value)
    }
}

forOf([1, 2, 3], console.log)
// 1 2 3

for await...of语法

在开发过程中,AJAX请求返回的Promise对象是很常见的。

我们用setTimeout模拟异步请求,有下列代码:

代码语言:javascript
复制
function asyncValue(value) {
    return new Promise(resolve => {
        setTimeout(() => resolve(value), value * 100)
    })
}


(async() => {
    const arr = [2, 4, 5].map(asyncValue)

    for await (const i of arr) {
        console.log(i)
    }
    // 2 4 5
})()

for await...of语法可以来遍历一个Promise对象组成的数组。

展开语法

不管是迭代器生成器,都支持es6的展开语法:

代码语言:javascript
复制
function* values() {
    yield 1
    yield 2
    yield 3
}

console.log([...values()])
// [ 1, 2, 3 ]

不做过多的演示了,只需知道展开语法也利用了迭代器生成器即可。

异步迭代器

接下来,我们继续回到range对象上,上面的相关语法只是起一个过渡作用。

给对象一个Symbol.asyncIterator属性,支持它被for await...of迭代:

代码语言:javascript
复制
function asyncValue(value) {
    return new Promise(resolve => {
        setTimeout(() => resolve(value), value * 100)
    })
}

function asyncRange(end) {
    let start = 0

    return {
        async next() {
            if (start === end) {
                return { done: true }
            }

            const value = await asyncValue(start++)
            return { value, done: false }
        },

        [Symbol.asyncIterator]() {
            return this
        }
    }
}

(async() => {
    for await (const i of asyncRange(5)) {
        console.log(i)
    }
    // 0 1 2 3 4
})()

异步生成器

就是加一个async关键字:

代码语言:javascript
复制
function asyncValue(value) {
    return new Promise(resolve => {
        setTimeout(() => resolve(value), value * 100)
    })
}

async function* asyncRange(end) {
    for (let i = 0; i < end; i++) {
        yield await asyncValue(i)
    }
}

(async() => {
    for await (const i of asyncRange(5)) {
        console.log(i)
    }
    // 0 1 2 3 4
})()
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 源码
  • 背景
  • 引入
  • 同步迭代器
  • 同步生成器
  • 相关语法
    • for...of语法
      • for await...of语法
        • 展开语法
        • 异步迭代器
        • 异步生成器
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档