前言
在ES6中引入了迭代器的概念,它是一种遍历数据集合的机制,提供了一种简单而一致的方式来访问集合中的每个元素,在集合与映射这篇文章中,我们就已经初步认识了Symbol.iterator这个概念。
概念
迭代器是通过迭代协议实现的,每一个拥有该协议的对象都可以称作是可迭代的对象,这个协议的标识就是Symbol.iterator,它是ES6中引入的一个新的Symbol值,表示一个对象是否有可迭代性。可迭代对象的Symbol.iterator属性是一个函数,我们称为迭代器对象,运行这个函数会返回一个next函数(next函数可以当成是JS的原型链或者C中链表的指针),运行next函数后又可以接收两个属性:value和done;value表示当前迭代的值;done表示遍历完全部迭代。
在JS中可迭代的对象有以下几种:
- Array(数组)
- Map(映射)
- Set(集合)
- String(字符串)
- TypedArray(类数组)
- NodeLists(Dom节点)
需要注意的是Object没有迭代能力
优点
- 灵活性:迭代器提供了一套灵活的遍历机制,使用相同的语法和操作方式于不同类型的数据结构
- 可迭代性:通过实现迭代器协议,可以让对象具有可迭代性,从而可以使用for of循环来遍历对象,增强代码可读性
- 惰性求值:迭代器是一种惰性求值的机制,只有在需要下一个元素时才会取值,这样可以节省内存,提高性能
- 可中断性:迭代器可以在任意时刻中断遍历,而不需要遍历整个数据结构,提高代码的效率
- 支持函数式编程:迭代器是函数式编程中常用的工具,它可以和其他函数式编程的特性一起使用,例如高阶函数、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中实现了迭代器,此外,生成器是特殊的迭代器,它的使用与迭代器类似,最后实现了迭代器中的可终止迭代器,使迭代器拥有终断功能
以上就是全部内容了,感谢你的阅读,如果觉得文章不错的话,还望支持一下博主,谢谢~