JS的迭代器是啥?精读JS迭代器

本文最后更新于:1 年前

目录

前言

概念

优点

用法

对象迭代能力

为什么对象没有迭代器

迭代器的实现

生成器

基础语法

生成器传参

生成器委托

可终止迭代器

总结


前言

在ES6中引入了迭代器的概念,它是一种遍历数据集合的机制,提供了一种简单而一致的方式来访问集合中的每个元素,在集合与映射这篇文章中,我们就已经初步认识了Symbol.iterator这个概念。

概念

迭代器是通过迭代协议实现的,每一个拥有该协议的对象都可以称作是可迭代的对象,这个协议的标识就是Symbol.iterator,它是ES6中引入的一个新的Symbol值,表示一个对象是否有可迭代性。可迭代对象的Symbol.iterator属性是一个函数,我们称为迭代器对象,运行这个函数会返回一个next函数(next函数可以当成是JS的原型链或者C中链表的指针),运行next函数后又可以接收两个属性:value和done;value表示当前迭代的值;done表示遍历完全部迭代。

在JS中可迭代的对象有以下几种:

  1. Array(数组)
  2. Map(映射)
  3. Set(集合)
  4. String(字符串)
  5. TypedArray(类数组)
  6. NodeLists(Dom节点)

需要注意的是Object没有迭代能力

优点

  1. 灵活性:迭代器提供了一套灵活的遍历机制,使用相同的语法和操作方式于不同类型的数据结构

  2. 可迭代性:通过实现迭代器协议,可以让对象具有可迭代性,从而可以使用for of循环来遍历对象,增强代码可读性

  3. 惰性求值:迭代器是一种惰性求值的机制,只有在需要下一个元素时才会取值,这样可以节省内存,提高性能

  4. 可中断性:迭代器可以在任意时刻中断遍历,而不需要遍历整个数据结构,提高代码的效率

  5. 支持函数式编程:迭代器是函数式编程中常用的工具,它可以和其他函数式编程的特性一起使用,例如高阶函数、map、filter、reduce 等等

用法

简单介绍一下迭代器的用法,使用[Symbol.iterator]()运行迭代对象,获取next,运行next获取迭代值

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(arr[Symbol.iterator]().next()); // { value: 1, done: false }

我们可以借助while对数组执行遍历操作

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
const iterator = arr[Symbol.iterator]()
let next
while (next = iterator.next(), !next.done) {
    console.log(next.value); // 1~9
}

或者通过ES6新增的for of对迭代对象进行遍历,for of和for in类似,for in是遍历索引,for of是遍历属性值

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for (const iterator of arr) {
    console.log(iterator);
}

对象迭代能力

为什么对象没有迭代器

对象是一种无序的集合类型,与数组或类数组不同,它的属性名称是字符串或Symbol类型,并且不具备索引。因此对象不能像数组一样使用简单的循环结构来遍历属性值,而是需要通过一些特殊的方法来实现对象的迭代。

迭代器的实现

基于上面的用法,我们可以尝试在对象上实现迭代器

Reflect.defineProperty(Object.prototype, Symbol.iterator, {
    value: function () {
        const keys = Reflect.ownKeys(this).filter(it => it !== Symbol.iterator) // 过滤自身
        const len = keys.length
        let count = 0
        return {
            next: () => ({ value: this[keys[count++]], done: count > len })
        }
    }
})

可以看到,对象像迭代对象一样也可以通过迭代的方式获取到值

const obj: any = {
    name: "阿黄",
    age: 10,
    like: "meat"
}
const iterator = obj[Symbol.iterator]()
let next
while (next = iterator.next(), !next.done) {
    console.log(next.value); // 阿黄 10 meat
}

for (const iterator of obj) {
    console.log(iterator); // 阿黄 10 meat
}

生成器

生成器(Generator)是 ES6 中新增的一种特殊函数,它的语法与迭代器有共同之处,可以看作是一种特殊的迭代器,它通过function*关键字来定义。生成器函数的内部可以使用yield关键字来暂停函数的执行,并返回执行结果,同时也可以再次从暂停的位置开始执行。在函数外部使用next()函数来获取生成器的迭代值,结构与上面的迭代器相同,包含value和done值

基础语法

比如,我们使用一个数组来表示函数异步的内容,在内部使用yield对函数进行暂停操作,在外部使用generator.next()对内部暂停的结果取迭代值

function* start(list: any[]) {
    let i = list.length
    while (i-- >= 0) {
        yield list[i]
    }
}
const arr = [1, 2, 3, 4, 5]
const generator = start(arr)
let next
while (next = generator.next(), !next.done) {
    console.log(next.value); // 5~1
}

生成器传参

在next函数中传入参数可以将参数带入生成器函数中,比如

function* start(q1: string) {
    const name: string = yield q1
    yield `我叫${name}`
}
const generator = start("你叫什么名字")
console.log(generator.next().value);// 你叫什么名字
console.log(generator.next("张三").value);// 我叫张三

生成器委托

生成器之间可以使用yield*关键字进行传递,函数中的多个生成器会连接在一起,一起输出值

function* getName(name: string) {
    yield `我叫`
    yield `${name}`
}
function* getAge(age: number) {
    yield `我${age}了`
}
function* getInfo(__name: string, __age: number) {
    yield* getName(__name)
    yield* getAge(__age)
}
const generator = getInfo("张三", 20)
console.log(generator.next().value);// 我叫
console.log(generator.next().value);// 张三
console.log(generator.next().value);// 我20了

上面的getInfo等同于

function* getInfo(__name: string, __age: number) {
    yield `我叫`
    yield `${__name}`
    yield `我${__age}了`
}

可终止迭代器

可终止迭代器在ES6中并未正式加入,但是却有相关的声明,使用return函数可以跳出迭代。使用throw函数可以跳出迭代并抛错,所以我们可以借助上面的对象迭代器的方式,实现一个迭代终止的功能

首先我们实现一个数组的迭代器,数组和对象区别在于已经存在length属性,所以我们做个兼容就行了

Reflect.defineProperty(Array.prototype, Symbol.iterator, {
    value: function () {
        const keys = Reflect.ownKeys(this).filter(it => it !== Symbol.iterator) // 过滤自身
        const len = this.length ?? keys.length
        let count = 0
        return {
            next: () => {
                const done = count >= len
                return { value: !done ? this[keys[count++]] : void 0, done }
            },
        }
    }
})
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
const iterator = arr[Symbol.iterator]()
let next
while (next = iterator.next(), !next.done) {
    console.log(next.value); // 1~9
}

然后我们在迭代器中增加return函数和throw函数

throw(err?: string) {
    throw new Error(err)
},
return() {
    count = len
    return { done: true };
}

接着在数组中使用一下

return函数:

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
const iterator: any = arr[Symbol.iterator]()
console.log(iterator.next());// { value: 1, done: false }
iterator.return()
console.log(iterator.next());// { value: undefined, done: true }

throw函数:

console.log(iterator.next());// { value: 1, done: false }
iterator.throw("抛个错")// Error: 抛个错

总结

本文介绍了JS迭代器的概念以及用法,并使用反射机制在Object中实现了迭代器,此外,生成器是特殊的迭代器,它的使用与迭代器类似,最后实现了迭代器中的可终止迭代器,使迭代器拥有终断功能

以上就是全部内容了,感谢你的阅读,如果觉得文章不错的话,还望支持一下博主,谢谢~