持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
迭代器模式在实际的应用场景不多,因为常用的JS“数据集合”已经实现了迭代器。但是在框架层面,如果实现了某种特定场景下的功能而设计了一种新的数据结果,我们又想使用for...of,那就要自己实现迭代器了。尽管实际应用场景不多,但是我觉得还是很有必要学习一下迭代器模式。
迭代器模式
在学习迭代器模式之前,我们需要弄明白两个基础概念,虽然基础,但是未必大部分人都能理解的很到位。
- 迭代(iterate) - 按顺序访问线性结构中的每一项(只能顺序依次访问);
- 遍历(traversal) - 按规则访问非线性结构中的每一项(可访问其中一段区间的元素);
基础
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
ES6实现迭代器的例子:
class Container {
constructor(list){
this.list = list;
}
getIterator() {
return new Iterator(this);
}
}
class Iterator {
constructor(container){
this.list = container.list;
this.index = 0;
}
next(){
let done = this.index >= this.list.length;
let res = {
value: this.list[this.index],
done: done
};
if(!done) {
this.index++;
}
return res;
}
}
// 测试代码
let arr = [5,6,9,88];
let container = new Container(arr);
let it = container.getIterator();
let tmp = it.next();
while(!tmp.done){
console.log(tmp.value);
tmp = it.next();
}
其实上面的代码会有点奇怪,既然使用了array数组,那我们直接for,或者while循环不就可以实现我们想要的功能了吗? 是的,没错,的确可以实现。但是只是人家给你封装好了迭代器,你直接使用就行了,不用关心内部怎么实现,但是如果不允许你使用js中的这些集合,比如array,map,set,array-like,你可以手动实现一个迭代器把集合中的元素都迭代出来吗?
实现目标
先来实现一个小目标,实现我们自己的迭代器。
目标:
- 实现自己的迭代器,把集合中的元素都迭代出来;
- 自己实现的迭代器要支持for(let item of iterator);
// 迭代器只要把集合中的元素都迭代出来就行;
// 迭代器不只可以迭代出来元素,还要支持下面的语法
for(let item of iterator){
...
}
迭代器中的集合必须是有序的集合,所以我们可以用链表来实现,第一步,我们先来实现一下把集合中的元素迭代出来,下一步再考虑怎么支持for...of。
小目标1
// 有序集合
class ListNode {
constructor(val){
this.val = val;
this.next = null;
}
}
// 2-6-8-5
let head = new ListNode(2);
let n1 = new ListNode(6);
let n2 = new ListNode(8);
let n3 = new ListNode(5);
head.next = n1;
n1.next = n2;
n2.next = n3;
class Container {
constructor(list){
this.list = list;
}
getIterator(){
return new Iterator(this);
}
}
class Iterator{
constructor(container){
this.list = container.list;
this.prev = this.list;
}
next(){
let done = (this.prev == null);
let res = {
value: done ? null : this.prev.val,
done
};
if(!done) {
this.prev = this.prev.next;
}
return res;
}
}
let container = new Container(head);
let it = container.getIterator();
let temp = it.next();
while(!temp.done){
console.log(temp.value);
temp = it.next();
}
小目标2
思考:
现在有一个数组,需要把数组中的元素都遍历出来,但是有个条件,就是不能通过数组的下标来访问元素,也不能通过for语句来实现循环,请问怎么把数组中的元素都遍历出来?
了解Symbol:
let obj = {
name: 'David',
age: 20,
[Symbol('my symbol')]: 666
};
console.log(obj, Object.keys(obj));
用内置迭代器来实现:
let pro = Object.getOwnPropertySymbols(arr.__proto__)[0];
let it = arr[pro]();
let tmp = it.next();
while (!tmp.done) {
console.log(tmp.value);
tmp = it.next();
}
// 也可以用for...of
for (let item of it) {
console.log(item);
}
有没有发现上面的代码(next,value,done)有点眼熟啊?是不是和之前说的generator函数就联系起来了,之前说过generator函数返回的正是一个迭代器。
class Iterator {
constructor(container) {
this.list = container.list;
this.prev = this.list;
}
[Symbol.iterator]() {
let _this = this;
return {
next() {
let done = (_this.prev == null);
let res = {
value: done ? null : _this.prev.val,
done
};
if (!done) {
_this.prev = _this.prev.next;
}
return res;
}
};
}
}
let container = new Container(head);
let it = container.getIterator();
for (let item of it) {
console.log(item);
}
大功告成!
总结
迭代器模式的核心,就是实现统一遍历的接口。
应用场景:
不同数据结构类型的 “数据集合”,需要对外提供统一的遍历接口,而又不暴露或修改内部结构时,可应用迭代器模式实现。比如在用for...of遍历集合的时候,我并不关心集合是array,map,set,也不关心他们的内部是怎么实现迭代器的。 \实际应用的场景不多,是因为常用的js的“数据集合”已经实现好了迭代器。但是如果在框架层面,如果实现了一种新的数据结构,又想使用for...of,那就必须自己实现迭代器。