迭代器模式
wangjing013 opened this issue · comments
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
如果对上面定义不理解,那么先接着看后面的内容。相信看完后,一定会恍然大悟。
为什么需要引入迭代器
从定义中能得出,其实迭代器模式要解决的根本问题是,不同聚合对象遍历的问题。如数组、链表、对象等,通常想要遍历某个对象时都需要针对类型去选取不同的方式.
例如数组 for + length,对象通过 Object.keys,链表相对复杂通过 next()
移动指针的方式。 这样其实带来理解上的成本。
迭代器离我们很近
在我们日常开发工作中,遍历是一种高频出现的需求。因为它的特殊性,所以在编程语言中都内置了迭代器。JS 也不例外,在 ES6 之前 JS 内部提供简陋的迭代器 Array.prototype.forEach
用于遍历数组对象,如下:
[1,2,3,4].forEach((item, index, arr)=> {
console.log(`当前元素: ${item}, 对应索引: ${index}``);
})
forEach 从定义确定它不是万能,它只适合应用在 Array 对象上。 现在要遍历类数组对象(eg: arguments、HTMLCollection ),对象时 forEach 就显得无能无力了。
当遍历类数组对象时,通常做法是先通过借助 Array.prototype.slice
把相应的类数组对象转换成数组对象,再进行相应遍历操作。
- 遍历类数组对象
const obj = {
length: 2,
'0': 1,
'1': 2
}
const arr = Array.prototype.slice.call(obj);
arr.forEach(function(item, index, arr){
console.log(`当前元素: ${item}, 对应索引: ${index}`);
});
- 遍历对象
const obj = {
name: 'wangjing',
age: 28
}
const keys = Object.keys(obj);
for(let key of keys) {
console.log(key, obj[key]);
}
从上面案例来看,在遍历不同集合类型时,首先需要知道它属于什么特定类型(关心内部结构),应用特定的方式。
遍历数组、对象、类数组都是一种常见需求,因为它被需要,所以很多库都提供通用的迭代器,例如 Jquery 中 map,lodash 中提供 forEach
、forEachRight
。
下面使用 lodash 中提供 forEach
来实现上面集合对象遍历,
import { forEach } from "lodash";
const arr = [1, 2, 3, 4];
forEach(arr, (value, index, arr) => {
console.log(value, index, arr);
});
const obj = {
name: "张三",
age: 20
};
forEach(obj, (value, index, obj) => {
console.log(value, index, obj);
});
const o = {
length: 2,
'0': 1,
'1': 2
}
forEach(o, (value, index, obj)=> {
console.log(value, index, obj)
})
细心人应该能发现,同样是迭代不同类型的对象,通过 lodash 提供 forEach 使得无需关心具体类型及内部的实现,这就是迭代器的价值所在。
随着 JS 的发展,同样提供一套统一迭代器规范。
ES6 对迭代器的实现
因为它的重要性,所以 ES6 定义一套统一接口规范(iterator),其次大部分内置对象中也都实现该规范,例如 Array、String、Map、Set、arguments、HTMLCollection。同样提供 for of 去遍历已实现迭代器的对象,这样达到统一的目的。
同样已前面的案例,现在通过 ES6 提供迭代器去完成相应的遍历。
数组
const arr = [1,2,3];
const iterator = arr[Symbol.iterator]()
iterator.next(); // {value: 1, done: false}
iterator.next(); // {value: 2, done: false}
iterator.next(); // {value: 3, done: false}
iterator.next(); // {value: undefined, done: true}
类数组对象
遍历 arguments
对象
function fn(){
const iterator = arguments[Symbol.iterator]()
iterator.next(); // {value: 1, done: false}
iterator.next(); // {value: 2, done: false}
iterator.next(); // {value: 3, done: false}
iterator.next(); // {value: undefined, done: true}
}
fn(1,2,3,4)
对象类型
对象默认没有实现迭代器,如果想要一个对象成为可迭代的对象,一个对象必须实现 @@iterator
方法。这里就不概述了,想要了解更多可以查看 迭代协议 。
const obj = {
name: "张三",
age: 20
};
obj[Symbol.iterator] = function(){
const keys = Object.keys(obj);
let index = 0
return {
next(){
return index < keys.length ? {
value: obj[keys[index++]]
done: false
} : {
value: undefined,
done: true
}
}
}
}
generator
generator
天生为迭代器的 API:
function* fn(){
yield 1;
yield 2;
yield 3;
}
var run = func();
run.next() // {value: 1, done: false}
run.next() // {value: 2, done: false}
run.next() // {value: 3, done: true}
我们无需关心 generator 内部是何种存储结构,只需要调用 .next()
,并根据返回的 done
来判断是否遍历完即可。在 generator 的场景中,迭代器不仅用来遍历聚合,还用于执行代码。
链表
const linkedList = {
value: 'n1',
next: {
value: 'n2',
next: {
value: 'n3',
next: null
}
},
[Symbol.iterator]() {
let head = this;
return {
next(){
if (head) {
let value = head.value;
head = head.next;
return {
value: value,
done: false
}
}
return {
value: undefined,
done: true
}
},
}
}
}
const iterator = linkedList[Symbol.iterator]()
console.log(iterator.next()) // { value: 'n1', done: false }
console.log(iterator.next()) // { value: 'n2', done: false }
console.log(iterator.next()) // { value: 'n3', done: false }
console.log(iterator.next()) // { value: undefined, done: true }
从上面例子中可以看到,我们都是通过 Symbol.iterator
获取对象的迭代器( iterator ),然后调用它 next()
方法进行遍历输出。
现在再回顾看看迭代器定义及它解决的问题,是不是不谋而合。到此迭代器相关的内容就告一段落啦!!!
总结
迭代器是一种简单的设计模式,甚至很多时候我们都不认为它是一种设计模式。相信在此之很多人都没有意识到迭代器离我们很近,且一直都在用。希望通过上面案例,让对迭代器有一定认知且掌握它背后的实现机制。