迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
JavaScript
有内置的迭代器Array.prototype.forEach
。
下面来实现一个迭代器函数each
,each
函数接受2个参数,第一个为一个数组,第二个为每一次循环后要触发的回调函数,代码如下:
function each(arr, callback) {
// 对arr循环遍历,每一次遍历调用callback
for (let i = 0, l = arr.length; i < l; i++) {
callback.call(arr[i], i, arr[i]);
}
}
刚才编写的each
函数属于内部迭代器,因为在each
函数内部已经定义好了迭代的规则,外部只需要调用一次就可以。
内部迭代器不关心具体的实现,因此调用时非常方便,但是这也刚好是内部迭代器的缺点,因为内部迭代器的迭代规则已经被提前规定好了,如果我们想同时迭代2个数组,上面的each
函数是无法实现的。
比如,判断2个数组里元素的值是否完全相等,不能改动each
函数,代码如下:
function compare(arr1, arr2) {
// 如果两个数组长度不相同,不可能相等
if (arr1.length !== arr2.length) {
console.log("arr1和arr2不相等");
return;
}
each(arr1, function (i, n) {
// i为arr1每一项索引,n为arr1每项的值
if (n !== arr2[i]) {
console.log("arr1和arr2不相等");
return;
}
console.log("arr1和arr2相等");
});
}
外部迭代器必须显式地请求迭代下一个元素,它增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。
实现一个外部迭代器:
function Iterator(obj) {
var current = 0; // 记录当前的索引
// 下一个位置的索引
var next = function () {
current += 1;
};
// 是否已经迭代完成
var isDone = function () {
return current >= obj.length;
};
// 获取当前位置的数据
var getCurrentItem = function () {
return obj[current];
};
return {
next,
isDone,
getCurrentItem,
};
}
根据以上外部迭代器,我们可以这样改写compare
函数:
function compare(iterator1, iterator2) {
while (!iterator1.isDone() && !iterator2.isDone()) {
if (iterator1.getCurrentItem() !== iterator2.getCurrentItem()) {
console.log("arr1和arr2不相等");
return;
}
iterator1.next();
iterator2.next();
}
console.log("arr1和arr2相等");
}
var iterator1 = Iterator([1, 2, 3]);
var iterator2 = Iterator([2, 3, 4]);
compare(iterator1, iterator2); // arr1和arr2不相等
外部迭代器虽然调用方式相对复杂,但它的适用面更广,也能满足更多变的需求。
迭代器模式不仅可以迭代数组,还可以迭代一些类数组的对象,比如arguments
、{ 0: "a", 1: "b" }
等。无论是内部迭代器还是外部迭代器,只要被迭代的聚合对象拥有length
属性而且可以用下标访问,那它就可以被迭代。
在JavaScript
中,for in
语句可以用来迭代普通字面量对象的属性。jQuery
中提供了$.each
函数来封装各种迭代行为:
$.each = function (obj, callback) {
var value,
i = 0,
length = obj.length,
isArray = isArraylike(obj);
if (isArray) {
// 迭代类数组
for (; i < length; i++) {
value = callback.call(obj[i], i, obj[i]);
if (value === false) {
break;
}
}
} else {
for (i in obj) {
// 迭代 object 对象
value = callback.call(obj[i], i, obj[i]);
if (value === false) {
break;
}
}
}
return obj;
};