JavaScript设计模式五(迭代器模式)
迭代器模式的定义:
迭代器模式的意思就是提供一种方法能够顺序的访问聚合对象中的各个元素,同时又不暴露该对象的内部表示。大多数浏览器已经支持了Array.prototype.forEach
由于兼容性问题,我们目前使用原生的forEach比较少,我们先看看jQuery中的each方法
jQuery中的迭代器
jQuery中提供了each方法,来迭代聚合对象例如:
$.each([1,2,3], function(i, n) {
console.log('下标:' + i);
console.log('对应的值:' + n);
});
如何自己实现一个迭代器
其实大家用到很多是for循环,我们利用for循环就可以很快的实现一个迭代器
cons each = function(ary, callback) {
for(let i = 0; i < ary.length; i++) {
callback.call(null, i, ary[i]);
}
}
each([1,2,3], function(i, n) {
console.log('下标:' + i);
console.log('对应的值:' + n);
});
内部迭代器与外部迭代器
迭代器根据不同的场景分为内部迭代器和外部迭代器
内部迭代器
上面的each函数就是属于内部迭代器,each函数的内部定义好了规则,外部只需要一次调用,不需要关心内部迭代器是如何实现的。但是由于内部迭代器的迭代规则被提前定义好了,each函数就没法同时迭代连个数组了,例如我们需要判断两个数组是否相等,这里就只能利用each的回调函数了。
var compare = function(array1, array2) {
if (array1.length !== array2.length) {
console.log('not equal');
throw new Error();
}
each(array1, function(i, n) {
if (array1[i] !== array2[i]) {
console.log('not equal');
throw new Error();
}
});
console.log('equal');
}
compare([1,2,3], [1,2,3]);
但是其实这里其实虽然满足了我们的需要,但是有一些缺点,大家可以自己思考一下
在ES6中,新增了3中内部迭代器,大家有兴趣可以了解一下:entries()、values()、keys()
外部迭代器
外部迭代器必须显式的请求迭代下一个元素,我们先看一下ES6中的迭代器Iterator
迭代器Iterator的遍历过程:
- 创建一个指针对象,指向当前数据结构的起始位置,也就是说迭代器的本质是一个指针对象
- 第一次调用指针对象的next方法,可以将指针指向第一个数据成员
- 第二次调用指针对象的next方法,可以将指针指向第二个数据成员
- 不断调用指针对象的next方法,直到指向数据结构的结束位置
在ES6中,一般迭代器和生成器一起使用
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
let iterator = createIterator();
iterator.next();
iterator.next();
iterator.next();
这里每当执行一次yield时,函数会停止执行,直到再次调用迭代器的next方法。老生长谈的问题,我们看看ES5中如何实现类似的效果
function createIterator(items) {
var i = 0;
var done = (i >= items.length);
var value = !done? items[i]: void 0;
return {
next: function() {
done = (i >= items.length);
value = !done? items[i++]: void 0;
return {
done: done,
value: value,
};
},
isDone: function() {
return done;
},
getCurrItem: function(){
return value;
}
}
}
var iterator = createIterator([1,2,3]);
iterator.next();
iterator.next();
iterator.next();
iterator.next();
那我们在回到上面的问题如何判断两个数组是否相等的问题?
var compare = function(iterator1, iterator2) {
while(!iterator1.isDone() && !iterator2.isDone()) {
if (iterator1.getCurrItem() !== iterator2.getCurrItem()) {
console.log('not equal');
};
iterator1.next();
iterator2.next();
}
console.log('equal');
}
var iterator1 = createIterator([1,2,3]);
var iterator2 = createIterator([1,2,3]);
compare(iterator1, iterator2);
这两种迭代器没有优劣之分,主要是根据场景来定
迭代类数组对象和字面量常量
不仅仅是数组可以被迭代,对象、arguments这些有length属性,并且可以利用下标访问的都可以被迭代。在ES6中规定了可迭代的对象需要具备Symbol.iterator属性,具体可以参考阮一峰的ES6入门。一般来说我们对于数组采用的是for循环,对于字面量对象可以利用for in来迭代
迭代器模式应用例子
例如我们需要做一个文件上传的代码,开始是这样的:
var getUploadObj = function() {
try {
// IE的上传组件
return new ActiveXObject("TXFTNActiveX.FTNUpload")
} catch (e){
if (supportFlash()) {
var str = '';
return $(str).appendTo($('body'));
} else {
var str = ''
return $(str).appendTo($('body'));
}
}
}
这段代码本身没啥问题,我们可能平常就是这么写的,但是是否有优化的空间呢?
这些try catch还有if else总是感觉不够友好,而且如果我们增加了html5的上传方式,我们又需要修改getUploadObj函数,这样大家好像想到了方法,把这3中上传方式各自封装成方法
var getActiveUploadObj = function(){
try {
// IE的上传组件
return new ActiveXObject("TXFTNActiveX.FTNUpload")
} catch (e){
return false;
}
}
var getFlashUploadObj = function() {
if (supportFlash()) {
var str = '';
return $(str).appendTo($('body'));
}
return false;
}
var getFormUploadObj = function() {
var str = ''
return $(str).appendTo($('body'));
}
var uploadObj = iteratorObj(getActiveUploadObj, getFlashUploadObj, getFormUploadObj);
function iteratorObj() {
for(var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === 'Function') {
var uploadObj = arguments[i]();
if (uploadObj !== false) {
return uploadObj;
}
}
}
}