Skip to main content

迭代器模式(行为型)

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。 ——《设计模式:可复用面向对象软件的基础》

迭代器模式是设计模式中少有的目的性极强的模式。所谓“目的性极强”就是说它不操心别的,它就解决这一个问题——遍历。

一、ES6 迭代器的实现

ES6 新增了 SetMap,如今 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' }

这里为了记录每次遍历的位置,实现了一个闭包,借助自由变量来做迭代过程中的游标。