《JavaScript设计模式与开发实践》——第七章(迭代器模式)学习记录

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
迭代器模式无非就是循环访问聚合对象中的各个元素,比如我们很熟悉的JQuery中的$.each:

$.each([1,2,3],function(i,n){
	//i为当前索引
	//n为当前元素
})

下面我们来实现一个自己的each函数,同样接受两个参数,一个是被循环的数组,另一个是循环中的每一步后将被触发的回调函数:

	var each = function(ary,callback){
      for (let i = 0; i < ary.length; i++) {
        callback.call(ary[i],i,ary[i])
      }
    }
    each(["哈哈","嘿嘿","吼吼"],function(i,item){
      console.log(i,item)
    })

迭代器可以分为内部迭代器和外部迭代器,它们都有各自的适用场景。

内部迭代器
上边编写的each函数属于内部迭代器,each函数的内部已经定义好了迭代规则,它完全接手整个迭代过程,外部只需要一次初始调用。
内部迭代器在调用的时候非常方便,外界不用关心迭代器内部的实现,跟迭代器的交互也仅仅是一次初始调用,这也是内部迭代器的缺点,由于内部迭代器的迭代规则已经被提前规定,上面的each函数无法同时迭代两个数组了。
比如现在有一个需求,需要判断2个数组里元素的值是否完全相等,如果不改写each函数本身的代码,我们能够入手的地方似乎只剩下each地回调函数了:

	var compare = function(ary1,ary2){
      if(ary1.length!=ary2.length){
        throw new Error('ary1和ary2不相等');
      }
      each(ary1,function(i,n){
        if(n!=ary2[i]){
          throw new Error('ary1和ary2不相等');
        }
      })
      console.log('ary1和ary2相等')
    }
    compare([1,2,3],[1,2,4])// Uncaught Error: ary1和ary2不相等

外部迭代器
外部迭代器必须显示地请求迭代下一个元素。
外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序,看下下一段代码:

 	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,
        length:obj.length
      }
    }
    var compare = function(iterator1,iterator2){
      if(iterator1.length!=iterator2.length){
        throw new Error('iterator1和iterator2不相等');
      }
      while(!iterator1.isDone() && !iterator2.isDone()){
        if(iterator1.getCurrItem() != iterator2.getCurrItem()){
          throw new Error('iterator1和iterator2不相等');
        }
        iterator1.next();
        iterator2.next();
      }
    
      console.log('iterator1和iterator2相等')
    }
    var iterator1 = Iterator([1,2,3]);
    var iterator2 = Iterator([1,2,3]);
    compare(iterator1,iterator2)// Uncaught Error: iterator1和iterator2不相等

外部迭代器虽然调用方式相对负责,但它的适用面更广,也能满足更多变的需求。内部迭代器和外部迭代器在实际生产中没有优劣之分,究竟使用哪一个要根据场景而定。

迭代类数组对象和字面量对象
迭代器模式不仅可以迭代数组,还可以迭代一些类数组的对象。比如arguments,{“o”:‘a’,“1”:‘b’}等。无论哪种迭代器,只要被迭代的聚合对象拥有length属性而且可以用下标访问,那它就可以被迭代。
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
    }

倒序迭代器
分分钟实现一个倒序访问的迭代器

	var reverseEach = function(ary,callback){
      for (let i = ary.length-1; i >=0; i--) {
        callback(i,ary[i])
      }
    }
    reverseEach(["哈哈","嘿嘿","吼吼"],function(i,item){
      console.log(i,item)
    })

中止迭代器
迭代器可以像普通for循环中的break一样,提供一种跳出循环的方法,比如前面的那段jQuery代码中有一句:

 if(value === false){
         break;
   }

这句代码约定如果回调函数的执行结果返回false,则提前终止循环。下面把之前的each函数改写一下:

var each = function(ary,callback){
      for (let i = 0; i < ary.length; i++) {
        if(callback.call(ary[i],i,ary[i]) === false){
          break;
        }
      }
    }
    each(["哈哈","嘿嘿","吼吼"],function(i,item){
      if(item == "嘿嘿"){
        return false;
      }
      console.log(i,item)//0 哈哈
    })

迭代器模式的应用举例
以文件上传模块为例,下面这段代码的目的是根据不同的浏览器获取相应的上传组件对象:

	var getUploadObj = function(){
      try{
        return new ActiveXObject("TXFTNActiveX.FINUpload");//IE上传控件
      }catch(e){
        if(supportFlash()){//supportFlash函数未提供
          var str = ''
          return $(str).appendTo($('body'))
        }else{
          var str = '';//表单上传
          return $(str).appendTo($('body'))
        }
      }
    }

上面代码可以看出,缺点显而易见,1.很难阅读;2.严重违反了开闭原则。
梳理一下问题,目前有三种可能的上传方式,我们不知道目前正在使用的浏览器支持哪几种,就像我们有3把钥匙,我们想打开一扇门但是不知道该使用哪把,于是从第一把钥匙开始,迭代钥匙串进行尝试,直到找到了正确的钥匙为止。
同样,我们把每种获取upload对象的方法都封装在各自的函数里,然后使用一个迭代器,迭代获取这些upload对象,直到获取到一个可用的为止:

var getActiveXObject = function(){
  try{
    return new ActiveXObject("TXFTNActiveX.FINUpload");//IE上传控件
  }catch(e){
    return false;
  }
}

var getFlashUploadObj = function(){
  if(supportFlash()){//supportFlash函数未提供
      var str = ''
      return $(str).appendTo($('body'))
    }
    return false;
}

var getFormUpladObj = function(){
  if(supportFlash()){//supportFlash函数未提供
    var str = '';//表单上传
    return $(str).appendTo($('body'))
  }
  return false;
}

可以看到,在上面3个函数中,都有同一个约定:如果该函数里面的upload对象是可用的,则让函数返回该对象,反之返回false,提示迭代器继续往后面进行迭代
迭代器只需进行下面几步工作:
提供一个可以被迭代的方法,使得上面三个函数依照优先级被循环迭代。
如果正在被迭代的函数返回一个对象,则表示找到了正确的upload对象,反之如果该函数返回false,则让迭代器继续工作:

var iteratorUploadObj = function(){
      for(var i = 0,fn;fn = arguments[i++];){
        var uploadObj = fn();
        if(uploadObj!=false){
          return uploadObj;
        }
      }
    }
    var uploadObj = iteratorUploadObj(getActiveXObject,getFlashUploadObj,getFormUpladObj)

重构代码后,我们可以看到,获取不同上传对象的方法被隔离在各自的函数里互不干扰,我们可以很方便地维护和扩展代码,比如后来我们想给项目增加HTML5上传,我们只需要添加:

var getHtml5UploadObj = function(){
//具体代码略
}

然后按照优先级把它们添加进迭代器:

iteratorUploadObj(getActiveXObject,getFlashUploadObj,getFormUpladObj,getHtml5UploadObj)

你可能感兴趣的:(javascript设计模式,javascript,设计模式,迭代器模式)