前言
关于迭代器,在学生时期学习c++语言的时候就接触过这个概念,但一直都没有真正的理解,现在在总结数组方法时,又遇到的这个概念,因为数组中有一类方法称为迭代器方法,他们分别是:
我全局搜索了我们公司整个项目,对以上的这3个迭代器方法的使用率倒不是很高,但是你没有解决的问题总会再回来找你,不必了,这次就把它拿下。
前置知识
依赖倒置的软件设计思想
依赖倒置的软件设计思想是SOLID设计原则中的D,关于其他的设计原则,可以参考这位优秀掘友的文章 juejin.cn/post/719406…
在业务兑现代码的过程中,往往先是对需求进行拆解,在拆解的过程中,我们可以抽取或者封装出来一些可以复用的工具方法,或者地基功能块,而业务的实现往往是这样方法的堆砌或者地基模块的组合。
这种方法设计简单但弊端也很明显,就是底层地基一旦变化则会影响上层的业务实现,而采用依赖倒置设计思想设计的软件则可以实现底层和业务隔离的效果,从一定程度上增强了软件框架的稳定性。
依赖倒置设计思想的核心就是在底层方法和上层业务之间引入一个抽象层。引入抽象层后,高层次业务不再依赖低层次模块或者方法,而是依赖抽象接口,而底层次方法实现对应的抽象接口。即使实现接口的方式变化,上层业务也是无感的。
举个生活中的例子,比如有个生产鞋子的业务: 在生产鞋子的链路上,设计师是高层次类,而组成鞋子部件如(鞋带、鞋底、鞋面等)的部件生产商/供应商是低层次类,在设计师和生产商之间,有个中间层,也就是根据人体工学进行抽象出了一个鞋子模型(广义上的鞋子模型,如今鞋子品类繁多,此处仅用于举例,不太严谨),设计师面向鞋子模型做设计,而底层生产商面向鞋子模型生产零部件,设计师有新的想法落地会导致生产商可能需要更换生产原料或者增加新的生产品种,但是底层生产商内部的一些变化对设计师是无感的,比如生产商更使用了更换了成本更低的材料,但能达到和之前一样的效果等。
伪代码:
class Ishose{
void shoelaces();
void shoesole();
void vamp();
}
//有一天 disgner 设计了一个fancy shoelaces
class Ishose{
shoelaces();
fancyshoelaces();
shoesole();
vamp();
}
class supplierA extend Ishose{
fancyshoelaces(){
console.log('我们家工厂生产的优质fancy鞋带')
}
...
}
class supplierB extend Ishose{
fancyshoelaces(){
console.log('我们家工厂生产的尼龙fancy鞋带')
}
...
}
// 子类重写父类的方法,来完成自己的实现,这里的父类是个抽象类 没有具体实现,更像一个标准
设计模式之迭代器模式
说了这么多,所以这个依赖倒置跟跟迭代器有什么关系啊,大有关系,迭代器模式就是建立这种软件设计思想之上,首先我们知道迭代就是遍历,迭代的业务场景就是遍历,此时对于高层次的我们(程序员或者用户) 需求是遍历,我们不关心底层数据在底层计算机上的存储方式以及数据结构 我们想要就是一个逐一审视的过程,于是通过抽象出来一个迭代器抽象层,不同的数据结构都需要实现一套自己的迭代器接口来达到这个目的。
图片仅供参考
js中很多内置类型都实现了Iterable接口
- 字符串
- 数组
- 映射
- 集合
- arguments对象
- NodeList对象
迭代器概念
可迭代协议
因为迭代的本质是遍历,符合可迭代协议的事物需要满足
1⃣️包含的元素是有限的
2⃣️具有无歧义的遍历顺序
3⃣️开始和结束有明确的标志
符合了可迭代协议后,才有可能实现抽象层Iterable接口,JS中很多内置类型符合可迭代协议且都实现了Iterable接口,如
- 字符串
- 数组
- 映射
- 集合
- arguments对象
- NodeList对象
(我说重话了不好意思 再看一遍加深记忆)
迭代器协议
对于多种符合可迭代协议的数据类型,在实现抽象接口时如何统一设计,迭代器协议就是在说抽象层接口规范。
迭代器对象必须实现一个next方法,使用迭代器next方法在可迭代对象中遍历数据,next方法返IteratorResult对象,IteratorResult对象 包含两个属性 done 和 value
done:布尔值 表示是否还可以调用next()取得下一个值 取值为true是表示耗尽即遍历结束
value:迭代对象的下一个值,下一个要遍历的值,耗尽时为undefined
在ECMAScript中,实现了Iterable接口的内置类型都拥有一个特殊的属性Symbol.iterator,这个属性值是迭代器的工厂函数,就是说调用它 Symbol.iterator() 会返回一个迭代器实例。
let arr = [1,2,3];
let iter = arr[Symbol.iterator]();
for(let item of arr){ console.log(item) }
//1
//2
//3
for(let item of iter){ console.log(item) }
//1
//2
//3
如上例子中所示,调用arr的Symbol.iterator方法返回了arr数组对象的迭代器实例,遍历的结果和直接遍历数组得到的结构一模一样,说明对于实现了Iterable接口的内置类型,for-of循环相当于帮我们在内部调用迭代器对象上的next方法
在JS中实现了迭代器接口的数据类型将可以执行如下操作:
- for-of循环
- 数组解构
- 扩展运算符
- Array.from()
- 创建集合Set
- 创建映射Map
- Promise.all() 接受由Promise组成的可迭代对象
- Promise.race() 接受由Promise组成的可迭代对象
回到数组中的迭代器方法
现在我们在回过头来看这3个迭代器方法呢
我是这么理解的 这三个方法 与 arr对象上Symbol.iterator 没有本质区别,他们也是调用后可以返回迭代器对象的工厂函数。
数组中的迭代器方法keys、values、entries 同样也实现了符合迭代器协议的next方法 只是说他们实现的next方法返回IteratorResult对象中的value属性各不相同
落实到实际使用上 ,根据这3个方法不同的功能,
在仅仅需要获取数组索引的遍历场景里,使用keys方法
在仅仅需要获取数组每个值的遍历场景里,可以使用values方法
在同时需要获取数组每个元素的【索引-值】的遍历场景里,可以使用entries方法
而以上三种场景 其实都可以使用forEach实现,forEach方法时es5新增的通用迭代方法,缺点是没有办法标识迭代何时终止,如此依以来,就只适用于数组了。
总结
写到这里,我怎么感觉在数组这个场景下,嗯 不掌握这三个迭代器方法也无伤大雅,应该是不会影响你写业务的。我最初是抱着对数组中的3个迭代器方法的学习来学习迭代器的,虽然这3个迭代器方法可以使用forEach或者for循环遍历也可以达到获取数组索引和值的目的,但是通过迭代器系统的学习,让我理解了软件设计模式迭代器模式,以及遇事不决加一层的依赖倒置的软件设计思想,希望后续在有关迭代器的学习场景上能更加得心应手。
学习前: 不懂,不敢随便用
学习后:会用,用不用取决于自己
本人是c/c++开发转行前端后野蛮生长的前端工程师,目前还在一点一点夯实基础中....
希望和大家一起学习,共同进步❤️
如果有帮助🫡点个收藏不迷路 ❤️
参考资料: 《红宝书》