迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
迭代器模式无非就是循环访问聚合对象中的各个元素。比如jQuery中的$.each函数,其中回调函数中的参数i为当前索引,n为当前元素,如下:
$.each([1,2,3],function(i,n){
console.log(‘当前下标为:’:+i);
console.log(‘当前值为:’+n);
});
现在我们可以自己来实现一个each函数,each函数接受2个参数,第一个为被循环的数组,第二个为循环中的每一步后将被触发的回调函数
var each = function(ary,callback){
for(var i=0,l=ary.length;i
迭代器可以分为内部迭代和外部迭代器,它们有各自的适用场景。这一节我们将分别讨论这两种迭代器。
1. 内部迭代器
我们刚刚编写的each函数属于内部迭代器,each函数的内部已经定义好了迭代规则,它完全接手整个迭代过程,外部只需要一次初始调用。
内部迭代器在调用的时候非常方便,外界不用关心迭代器的内部实现,跟迭代器的交互也仅仅是一次初始调用,但这也刚好蚋部迭代器的缺点,由于内部迭代器的迭代规则已被提前规定,上面的each函数就无法同时迭代2个数组
比如现在有个需求,要判断2个数组里元素的值是否完全相等,如果不改写each函数本身的代码,我们能够入手的地方似乎只剩下each回调函数,如下:
var compare =function(arr1,arr2){
if(arr1.length!=arr2.length){
throw new Error(‘ar1和ar2不相等’);
}
each(arr1,function(i,n){
if(n!=arr2[i]){
throw new Error(‘ary1和ary2不相等’);
}
});
alert(‘相等’);
}
compare([1,2,3],[1,2,4])//不等
2. 外部迭代器
外部迭代器必须显式地请求迭代下一个元素。
外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。
下面这个外部迭代器的实现来自《松本行弘的程序世界》用Ruby写成,这里我们翻译成js:
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:next,
isDone:isDone,
getCurrentItem: getCurrentItem
}
}
再看看如何改写compare函数:
var compare =function(iterator1,iterator2){
while(!iterator1.isDone() &&!iterator2.isDone()){
if(iterator1.getCurrentItem() !==iterator2.getCurrentItem()){
throw new Error(“iterator1和iterator2不相等’);
}
iterator1.next();
iterator2.next();
}
}
var iterator1 =Iterator([1,2,3]);
var iterator2 =Iterator([1,2,3]);
compare(iterator1,iterator2);//输出
外部迭代器虽然调用方式相对复杂,但它的适用更广,也能满足更多变的需求。内部迭代器和外部迭代器在实际生产中没有优劣之分,究竟使用哪个要根据需求场景而定。
迭代器模式不仅可以迭代数组,还可以迭代一些类数组对象。比如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
由于GoF中对迭代器的模式的定义非常松散,所以我们可以有多种多样的迭代器实现。总的来说,迭代器模式提供了循环访问一个聚合对象中每个元素的方法,但它没有规定我们以顺序、倒序还是中序来循环遍历聚合对象
倒序遍历对象:
var reverseEach = function(ary,callback){
for(var l =ary.length-1;l>=0;l--){
callback(l,ary[l]);
}
}
reverseEach([0,1,2], function(i,n){
console.log(n);
}
迭代器可以像普通for循环中的break一样,提供一种跳出循环的方法。在上一节中的each函数里有这样一句:
if(value == false){
break;
}
这句代码的意思是,约定如果回调函数的执行结果返回false,则提前终止循环。下面我们把之前的each函数改写一下:
var each = function(ary,callback){
for(vari=0,l=ary.length;i3){
return false; //n大于 3终止循环
}
console.log(n);
});
作者曾经要重构一个模块的代码,发现下面的这段代码,它的目的是根据不同的浏览器获取相应的上传组件对象:
var getUploadObj = function(){
try{
return new ActiveXObject(‘TXFINActiveX.FTNUpload”); //ie上传
}catch(e){
if(supportFlash()){
var str = ‘’;
return $(str).appendTo($(‘body’));
}else{
var str = ‘’;
return $(str).appendTo($(‘body’));
}
}
}
现在来梳理一下问题,目前一共有3种可能上传的方式,我们不知道目前正在使用的浏览器支持哪几种。就好比我们有一个钥匙串,其中共有3把钥匙,我们想打开一扇门但是不知道用哪把钥匙,于是从第一把开始,迭代钥匙串进行尝试,直到找到正确的钥匙为止。
同样,我们把每种获取upload对象的方法都封装在各自的函数里,然后使用一个迭代器迭代获取这些upload对象,直到获取到一个可用的为止:
var getActiveUploadObj = function(){
try{
return new ActiveXObject(“TXFNActiveX.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’));
}
在上述的3个函数中我们都有同一个约定,如果该函数里面的upload对象是可用的,则让函数返回该对象,反之返回false,提示迭代器继续往后进行迭代
var iteratorUploadObj = function(){
for(var i=0,fn;fn=arguments[i++];){
var uploadObj =fn();
if(uploadObj!==false){
return uploadObj;
}
}
}
var uploadObj = iteratorUploadObj(getActiveUploadObj,getFlashUploadObj, getFormUploadObj);
如后期还要支持html5的或是webkit的只要添加相应的函数就OK