1.定义
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素
2.迭代器
迭代器就是一个数据可以调用这个迭代器,他会把这个数据里面的每个元素顺序的返回出来,例如Array.prototype.forEach
3.实现一个迭代器
实现一个each函数,接受两个参数,第一个为循环的数组,第二个为循环后调用的回调函数
var each = function (arr, callback) {
for (let i = 0, l = arr.length; i < l; i++) {
callback.call(arr[i], i, arr[i]);
}
};
each([1, 2, 3], function (i, item) {
console.log(i, item);
});
4.内部迭代器和外部迭代器
使用迭代器比较两个数组元素是否完全相等
内部迭代器
each 函数属于内部迭代器,each 函数的内部已经定义好了迭代规则,它完全接手整个迭代过程,外部只需要一次初始调用,缺点是不能控制迭代规则,如果要同时迭代两个数组就不行了。
var compare = function (arr1, arr2) {
each(arr1, function (i, n) {
if (n !== arr2[i]) {
throw new Error("arr1和arr2不相等");
}
});
};
compare([1, 2, 3], [1, 2, 4]); // arr1和arr2不相等
外部迭代器
外部迭代器必须显式地请求迭代下一个元素。 外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序
// 外部迭代器
var Iterator = function (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,
};
};
var compare2 = function (iterator1, iterator2) {
while (!iterator1.isDone() && !iterator2.isDone()) {
if (iterator1.getCurrentItem() !== iterator2.getCurrentItem()) {
throw new Error("arr1和arr2不相等");
}
iterator1.next();
iterator2.next();
}
};
compare2(Iterator([1, 2, 3]), Iterator([1, 2, 4])); // arr1和arr2不相等
5.其他迭代器
迭代类数组和子面量对象
迭代器不止可以迭代数组,还可以迭代类数组如(arguments),只要被迭代的聚合对象拥有length属性而且可以用下标访问,那么它就可以被迭代
还可以通过for in去迭代对象
倒叙迭代器
由于 GoF 中对迭代器模式的定义非常松散,所以我们可以有多种多样的迭代器实现。总的来说, 迭代器模式提供了循环访问一个聚合对象中每个元素的方法,但它没有规定我们以顺序、倒序还是中序来循环遍历聚合对象
var reverseEach = function (ary, callback) {
for (var l = ary.length - 1; l >= 0; l--) {
callback.call(ary[l], l, ary[l]);
}
};
reverseEach([1, 2, 3], function (i, item) {
console.log(i, item);
});
中止迭代器
迭代器可以像普通for 循环中的break 一样,提供一种跳出循环的方法
var each = function (arr, callback) {
for (let i = 0, l = arr.length; i < l; i++) {
if (callback.call(arr[i], i, arr[i]) === false) {
break // 跳出循环
}
}
};
each([1, 2, 3], function (i, item) {
if (item > 2) {
return false;
}
console.log(i, item);
});
6.迭代器模式的应用
业务场景:在不同浏览器环境下,选择上传的方式不一样,判断当前环境并获取对应上传组件
未使用迭代器模式写法
var getUploadObj = function () {
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
} catch (e) {
if (supportFlash()) {
// supportFlash 函数未提供
var str = '<object type="application/x-shockwave-flash"></object>';
return $(str).appendTo($("body"));
} else {
var str = '<input name="file" type="file"/>'; // 表单上传
return $(str).appendTo($("body"));
}
}
};
解析
这个getUploadObj 函数里面充斥了try,catch以及if 条件分支,有这些缺点
- 很难阅读
- 严重违反开放-封闭原则,函数耦合性太高,导致形成了脆弱和低内聚设计,如果要再加一种情况,又要改变
getUploadObj函数
所以我们可以把获取upload对象的方法都封装在各自的函数中,然后通过一个迭代器,迭代获取这些upload对象,直到获取一个可用的为止
使用迭代器模式写法
var getActiveXObject = function () {
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload");
} catch {
return false;
}
};
var getSupportFlash = function () {
if (supportFlash()) {
// supportFlash 函数未提供
var str = '<object type="application/x-shockwave-flash"></object>';
return $(str).appendTo($("body"));
}
return false;
};
var getFormUploadObj = function () {
var str = '<input name="file" type="file" class="ui-file"/>'; // 表单上传
return $(str).appendTo($("body"));
};
// 迭代器模式获取upload对象
var iteratorUploadObj = function () {
for (var i = 0, fn; (fn = arguments[i++]); ) {
var uploadObj = fn();
if (uploadObj !== false) {
return uploadObj;
}
}
};
var uploadObj = iteratorUploadObj(
getActiveXObject,
getSupportFlash,
getFormUploadObj
);
解析 重构代码之后,我们可以看到,获取不同上传对象的方法被隔离在各自的函数里互不干扰,try、catch 和 if 分支不再纠缠在一起,使得我们可以很方便地的维护和扩展代码。 如果之后需要添加其他的获取upload对象方法只需要
var getHtmlUploadObj = function () {
}
var getWebkitUploadObj = function () {}
var uploadObj = iteratorUploadObj( getActiveUploadObj, getFlashUploadObj, getFormUpladObj );
这样子降低了函数的耦合性,也符合了开放-封闭原则
总结
迭代器模式是一种相对简单的模式,简单到很多时候我们都不认为它是一种设计模式。目前的绝大部分语言都内置了迭代器。