迭代器模式(行为型)
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。 ——《设计模式:可复用面向对象软件的基础》
迭代器模式是设计模式中少有的目的性极强的模式。所谓“目的性极强”就是说它不操心别的,它就解决这一个问题——遍历。
一、ES6 迭代器的实现
ES6 新增了 Set 和 Map,如今 Array、Object、Set、Map 四种数据结构有着自己的内部实现,因此诞生了 Iterator 遍历器,提供一个统一的接口来对不同的数据结构进行遍历。
ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator
属性,一个数据结构只要具有Symbol.iterator
属性,就可以认为是“可遍历的”(iterable),准确地说,是被 for of
循环和迭代器的 next
方法遍历,而 for of
背后正是对 next
方法的反复调用。
ES6 中,针对 Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象等原生的数据结构都可以通过 for of
进行遍历:
const arr = [1, 2, 3]
const len = arr.length
for (item of arr) {
console.log(`当前元素是${item}`)
}
本质是借助数组的 Symbol.iterator
生成了对应的迭代器对象,通过反复调用迭代器对象的 next
方法访问了数组成员,例如:
const arr = [1, 2, 3]
// 通过调用 iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()
// 对迭代器对象执行 next,就能逐个访问集合的成员
iterator.next()
iterator.next()
iterator.next()
输出结果如下:
可以看到 next 每次会按顺序访问一个集合成员,而 for of
做的事等价于:
// 通过调用 iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()
// 初始化一个迭代结果
let now = { done: false }
// 循环往外迭代成员
while (!now.done) {
now = iterator.next()
if (!now.done) {
console.log(`现在遍历到了${now.value}`)
}
}
可以看到,for of
其实就是 iterator 循环调用换了种写法,ES6 中用 for of
遍历各种各种的集合,全靠迭代器模式在背后给力。因此,for of
遍历普通对象会报错,可以给对象添加一个 [Symbol.iterator]
属性并指向一个迭代器来解决:
const obj = {
a: 1,
b: 2,
c: 3
};
obj[Symbol.iterator] = function* () {
const keys = Object.keys(obj);
for (let k of keys) {
yield obj[k]
}
};
for (let val of obj) {
console.log(val);
}
// 依次输出 1 2 3
二、实现迭代器生成函数
ES6 在可遍历数据结构中内置了 Symbol.iterator
属性,可以通过这个属性返回一个遍历器对象,并调用 next
方法来获取当前成员的信息对象:
const arr = ['a', 'b', 'c'];
const iterator = arr[Symbol.iterator]();
iterator.next() // { value: 'a', done: false }
iterator.next() // { value: 'b', done: false }
iterator.next() // { value: 'c', done: false }
用 ES5 也可以实现一个迭代器生成函数:
// 定义迭代器生成函数,入参是任意集合
function iteratorGenerator(items) {
// index 记录当前访问的索引
let index = 0
return {
// 自定义 next 方法
next: function () {
// 将当前值与遍历是否完毕(done)返回
return {
// 如果 done 为 false,则可以继续取值
done: index >= items.length,
// 如果索引还没有超出集合长度,done 为 false
value: index >= items.length ? undefined: items[index++],
}
}
}
}
const arr = ['a', 'b', 'c'];
const iterator = iteratorGenerator(arr);
iterator.next() // { done: false, value: 'a' }
iterator.next() // { done: false, value: 'b' }
iterator.next() // { done: false, value: 'c' }
这里为了记录每次遍历的位置,实现了一个闭包,借助自由变量来做迭代过程中的游标。