JavaScript设计模式笔记-迭代器模式

定义

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表现.迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即时不关心对象的内部构造,也可以按照顺序访问其中的每个元素.

7.1 jquery中的迭代器

迭代器模式无非就是循环访问聚合对象中的各个元素.比如jquery的$.each函数 其中i为当前索引,n为当前元素

$.each([1,2,3],function(i,n){ 
   console.log('当前下标为:'+i) 
   console.log('当前值为:'+n) 
}) 

7.2 实现自己的迭代器

var each=function(arr,cb){ 
   for(var i=0;i=arr.length;i<l;i++){ 
       cb.call(arr[i],i,arr[i]) 
   } 
} 
each([1,2,3],function(i,n){ 
   console.log(i,n) 
}) 

7.3 内部迭代器和外部迭代器

1.内部迭代器

刚刚编写的each属于内部迭代器,each函数内部已经定义好了迭代规则,它完全接收整个规则,外部只需要一次调用.内部迭代器在调用的时候十分方便,外界不用关心迭代器内部的实现,跟迭代器的交互也仅仅是一次初始调用,但也刚好是内部迭代器的缺点.比如需要比较判断两个数组元素值是否完全相等,如果不改写each函数内部,我们能够入手的地方也仅仅剩余each的回调函数了.

var compare=function(arr1,arr2){ 
   if(arr1.length!==arr2.length){ 
       throw new Error("arr1与arr2不相等") 
   } 
   each(arr1,function(i,n){ 
       if(n !== arr2[i]){ 
           throw new Error("arr1与arr2不相等) 
       } 
   }) 
   alert("arr1与arr2相等") 
} 
compare=([1,2,3],[1,2,4]);//error 不相等 

这个函数之所以能够如此顺利的完成,还依靠了JavaScript中函数可以作为参数传递这个特性,但是在其他语言中就未必有这么幸运了

2.外部迭代器

外部迭代器必须显式的请求迭代下一个元素.外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。下面实现一个外部迭代器 来自RUBY语言 出自《松本行弘的程序世界》

var Iterator=function(obj){ 
   var current=0; 
   var next=function(){ 
       current+=1; 
   }; 
   var isDone=function(){ 
       return current>=obj.length; 
   }; 
   var getCurrItem=function(){ 
       return obj[current] 
   }; 
   return{ 
       next:next, 
       isDone:isDone, 
       getCurrItem:getCurrItem 
   } 

} 

再看看如何改编compare函数

var compare=function(iterator1,iterator2){ 
   while(!iterator1.isDone()&&!iterator2.isDone()){ 
       if(iterator1.getCurrItem()!==iterator2.getCurrItem()){ 
           throw new Error("arr1与arr2不相等") 
       } 
       iterator1.next() 
       iterator2.next() 
   } 
   alert("arr1与arr2相等) 
} 
var iterator1=Iterator([1,2,3]); 
var iterator2=Iterator([1,2,3]); 
compare(iterator1,iterator2);//相等 

前几天正好有个问题.问题是如何异步回调的控制函数迭代..看到今天这章突然发现外部迭代器可以很好的实现这个需求.先来用递归实现的

var funcFor=function(arr,cb){ 
           var len=arr.length; 
           var i=0; 
           var Iterator=function(fn){ 
               fn(function(next,data) { 
                   if (next) { 
                       i++; 
                       if (i >=len) { 
                           return cb("没有了") 
                       } 
                       Iterator(arr[i]) 
                   } else { 
                       cb(data) 
                   } 
               }) 
           } 
           return function(){ 
               Iterator(arr[i]) 
           }() 
       } 
funcFor([function(cb){ 
   setTimeout(function(){ 
           cb(true) 
       },1000) 
   },function(cb){ 
   setTimeout(function(){ 
           cb(false,1) 
       },1000) 
   }],function(data){console.log(data)}) 

这里值得注意的就是 用递归实现也就相当于手动控制了迭代的进行.这段代码有个地方要注意下 为什么是i >=len而不是i > len 因为len与i是不一样的 i是从0开始算的. 那么用外部迭代器来改写 有稍许需要改动的

var Iterator=function(obj){ 
   var current=0; 
   var next=function(){ 
       current+=1; 
   }; 
   var isDone=function(){ 
       return current>=obj.length; 
   }; 
   var getCurrItem=function(){ 
       return obj[current] 
   }; 
   return{ 
       next:next, 
       isDone:isDone, 
       getCurrItem:getCurrItem 
   } 

} 

var funcIterator=Iterator([function(cb){ 
   setTimeout(function(){ 
           cb(true) 
       },1000) 
   },function(cb){ 
   setTimeout(function(){ 
           cb(false,1) 
       },1000) 
   }]) 

var compare=function(iterator,cb){ 
  if(!iterator.isDone()){ 
       var fn=iterator.getCurrItem(); 
       fn(function(next,data){ 
           if(next){ 
               if(!iterator.isDone()){ 
                   iterator.next() 
                   var fn=iterator.getCurrItem(); 
                   fn(arguments.callee) 
               } 
           }else{ 
               cb(data) 
           } 
       }) 
  } 
} 
compare(funcIterator,function(data){console.log(data)}) 

实际上这里也用到了递归.. 这里就利用了手动控制迭代器来完成了一个异步处理迭代的问题.

7.4 迭代类数组对象和字面量对象

迭代器不仅可以迭代数组,还可以迭代一些类数组对象,比如arguments,{“0”:1,”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){ 
               value=callback.call(obj[i],i,obj[i]); 
               if(value===false){ 
                   break 
               } 
           } 
       } 
       return obj; 
} 

7.5 倒序迭代器

由于GoF中对迭代器模式的定义非常松散,所以我们可以有多种多样的迭代器实现 比如实现一个倒序迭代器

var reverseEach=function(ary,callback){ 
   for(var l=ary.length-1;l>=0;i--){ 
       callback(l,ary[l]) 
   } 
} 
reverseEach([0,1,2],function(i,n){ 
   console.log(n);//分别输出2,1,0 
}) 

7.6 中止迭代器

迭代器可以像普通for循环中的break一样,提供一种跳出循环的方法,比如jquery中的each

if(value===false){ 
   break 
} 

参考上述的jquery迭代器比如需要n大于3的时候跳出循环

var each = function( ary, callback ){ 
   for ( var i = 0, l = ary.length; i < l; i++ ){ 
       if ( callback( i, ary[ i ] ) === false ){ // callback 的执行结果返回false,提前终止迭代 
           break; 
       } 
   } 
}; 

each( [ 1, 2, 3, 4, 5 ], function( i, n ){ 
   if ( n > 3 ){ // n 大于3 的时候终止循环 
       return false; 
   } 
   console.log( n ); // 分别输出:1, 2, 3 
}); 

7.7 迭代器模式的应用举例

..原文比较长 这里简单说一下 就是利用迭代器来判断选择哪个上传功能组件先看看没有用迭代器实现的判断

var getUploadObj=function(){ 
   try{ 
       return new ActiveXObject("TXFTNActiveX.FTNUpload");//IE上传控件 
   }catch(e){ 
       if(supportFlash()){ 
           var str="<object type="application/x-shockwave-false"></object>" 
           return $(str).appendTo($("body")); 
       }else{ 
           var str="<input name="file" type="file" />";//表单上传 
           return $(str).appendTo($("body")); 
       } 
   } 
} 

这里多了很多的if else判断 那么可以用策略模式来改写 策略模式就是专门未来处理这种问题的.下例的迭代器也比较像策略模式 只需要加一层context就行了

var getActiveUploadObj=function(){ 
    try{ 
       return new ActiveXObject("TXFTNActiveX.FTNUpload");//IE上传控件 
   }catch(e){ 
       return false 
   } 
} 
var getFlashUploadObj=function(){...略} 
var getFormUploadObj=function(){...略} 

那么我们的迭代器只需要进行下面这几步工作

  • 提供一个可以被迭代的方法 可以根据传递顺序的先后按照优先级进行迭代
  • 如果正在迭代的函数返回一个object则表示找到了正确的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) 

这个迭代器只需要封装一层就可以当做策略模式用了

7.8 小结

迭代器是一种相对简单的模式.简单到很多时候都不认为这是一种设计模式…….(简单嘛?)