- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
目录
迭代器模式介绍
JS中的遍历
- 迭代器存在的价值
ES6对迭代器的实现
-Iterator
Symbol.iterator 属性
- 实现一个迭代器生成函数
一 迭代器模式介绍
迭代器模式
提供一种方法顺序访问一个聚合对象中的各个元素
,而又不暴露该对象的内部表示
。 ——《设计模式:可复用面向对象软件的基础》
迭代器模式
,它就解决这一个问题——遍历
。
二 JS中的遍历
往往针对,
数组,对象,类数组,字符串
,等等不同的数据类型,遍历时,我们需要使用不同的遍历方法
. 比如 对象遍历的 7 种方法, 再比如,[JS基础回顾] 数组遍历的 10 种方法
比如:
1) for...of 不可遍历对象
却可以遍历数组
var obj = {
a:1,
b:2
};
for(item of obj){
console.log(item)
}
报错,obj不是一个迭代器.
var arr = [11,2,3];
for(item of arr){
console.log(item)
}
// 11 2 3
所以, 我们遍历不同的数据类型时,
需要考虑,我们遍历的是什么数据类型.
那么,迭代器不需要考虑数据类型,是更通用的遍历方案
看迭代器的定义是什么——
遍历集合的同时
,我们**不需要关心集合的内部结构
。而forEach,for,map...只能做到允许我们不关心数组这一种集合的内部结构,看来想要一套统一的遍历方案,我们非得请出一个更强的通用迭代器**
三 迭代器存在的价值
1) 最早的迭代器?-JQ迭代器
jQuery的迭代器为我们统一了
不同类型集合,比如:
JQ对象集合,
DOM对象(类数组)集合,
数组集合,的遍历方式
,使我们在访问集合内每一个成员时
不用去关心集合本身的内部结构
以及集合与集合间的差异
,这就是迭代器存在的价值
~
四 ES6对迭代器的实现
- Iterator
Symbol.iterator 属性
在ES6中新增了很多的特性,包括
Map,Set
等新的数据结构,算上数组和对象
已经有四种数据集合了,就像数组可以使用forEach
,对象可以使用for...in
进行遍历一样,是不是随着Map和Set的出现也应该给这两种集合一个遍历方法呢?
如果这样的话js的方法对象就太多了,既然这数组,对象,Map,Set 四种集合都是需要遍历的
,那么完全可以用一个统一的访问机制
。于是乎Iterator
应运而生。
1) Symbol.iterator 属性
与 for...of
与 迭代器的next方法
ES6约定,任何数据结构只要具备 Symbol.iterator 属性
(这个属性就是Iterator的具体实现
,它本质上是当前数据结构默认的迭代器生成函数
),就可以被遍历
——准确地说,是被for...of
和 迭代器的next方法
遍历。 for...of的背后正是对next方法的反复调用
2) for...of 除了
对象都可以遍历
是什么原因呢?我们看一下数组和对象实例的原型吧
在ES6中,针对
Array、Map、Set、String、LikeArray、函数的 arguments 对象、NodeList
对象这些原生的数据结构都可以通过for...of进行遍历
。原理都是一样的,此处我们拿最简单的数组进行举例,当我们用for...of遍历数组时:
3) for...of
遍历数组
var arr1 = [1, 2, 3]
var len = arr1.length
for(item of arr1) {
console.log(`${item}`)
}
4) 为什么for...of可以遍历数组呢?
之所以能够
按顺序一次一次地拿到数组里的每一个成员
,是因为我们借助数组的Symbol.iterator
生成了它对应的迭代器对象
,通过反复调用迭代器对象的next方法
访问了数组成员
5) 迭代器对象上的next
, arrSymbol.iterator.next()
var arr1 = [1, 2, 3]
// 通过调用iterator,拿到迭代器对象
var iterator1 = arr[Symbol.iterator]()
// 对迭代器对象执行next,就能逐个访问集合的成员
iterator1.next()
iterator1.next()
iterator1.next() // {value: 3, done: false}
iterator1.next() // {value: undefined, done: true}
6) for...of
就是 iterator循环调用
var arr1=[2,3,5,6]
// 通过调用iterator,拿到迭代器对象
var iterator1 = arr1[Symbol.iterator]()
// 初始化一个迭代结果
let now = { done: false }
// 循环往外迭代成员
while(!now.done) {
now = iterator1.next()
if(!now.done) {
console.log(now.value)
}
}
// 2 3 5 6
五 实现一个迭代器生成函数
迭代器生成函数帮我们
生成
迭代器对象
. 我们通过生成器(Generator
)来生成迭代器对象
:
1) 用ES6
的 生成器
编写一个迭代器生成函数
function *iteratorGenerator() {
yield '100'
yield '200'
yield '300'
}
var iterator = iteratorGenerator()
iterator.next(); // {value: '100', done: false}
iterator.next(); // {value: '200', done: false}
iterator.next(); // {value: '300', done: false}
iterator.next(); // {value: undefined, done: true}
2) 用ES5
去写一个能够生成迭代器对象
的迭代器生成函数
// 定义生成器函数,入参是任意集合
function iteratorGenerator(list) {
// idx记录当前访问的索引
var idx = 0
// len记录传入集合的长度
var len = list.length
return {
// 自定义next方法
next: function() {
// 如果索引还没有超出集合长度,done为false
var done = idx >= len
// 如果done为false,则可以继续取值
var value = !done ? list[idx++] : undefined
// 将当前值与遍历是否完毕(done)返回
return {
done: done,
value: value
}
}
}
}
var iterator = iteratorGenerator(['100', '200', '300'])
iterator.next(); // {value: '100', done: false}
iterator.next(); // {value: '200', done: false}
iterator.next(); // {value: '300', done: false}
iterator.next(); // {value: undefined, done: true}
此处为了记录每次遍历的位置
,我们实现了一个闭包
,借助自由变量
来做我们的迭代过程中的“游标”
迭代器模式比较特别,它非常重要,
重要到语言和框架都争着抢着帮我们实现
。但也正因为如此,大家业务开发中需要手动写迭代器的场景几乎没有,所以很少有同学会去刻意留意迭代器模式、思考它背后的实现机制。
参考
总结
迭代器模式
提供一种方法顺序访问一个聚合对象中的各个元素
,而又不暴露该对象的内部表示
。迭代器模式
,它就解决这一个问题——遍历
- 我们遍历不同的数据类型时,
需要考虑,我们遍历的是什么数据类型.
那么,迭代器不需要考虑数据类型,是更通用的遍历方案
- 使
我们在访问集合内每一个成员时
不用去关心集合本身的内部结构
以及集合与集合间的差异
,这就是迭代器存在的价值
- 既然这
数组,对象,Map,Set 四种集合都是需要遍历的
,那么完全可以用一个统一的访问机制
。于是乎Iterator
应运而生。 - ES6约定,任何数据结构只要具备
Symbol.iterator 属性
(这个属性就是Iterator的具体实现
,它本质上是当前数据结构默认的迭代器生成函数
),就可以被遍历
for...of
的背后正是对next方法
的反复调用
。- for...of
可以遍历数组
是因为我们借助数组的Symbol.iterator
生成了它对应的迭代器对象
,通过反复调用迭代器对象的next方法
访问了数组成员 迭代器生成函数
入参与返回
:列表 -> {}
迭代器生成函数
返回的对象
有一个next方法
,next方法
又返回一个对象{done:x, value:x}