文章出處

前面的話

  迭代器模式是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不需要暴露該對象的內部表示。迭代器模式可以把迭代的過程從業務邏輯中分離出來,在使用迭代器模式之后,即使不關心對象的內部構造,也可以按順序訪問其中的每個元素。迭代器模式是一種相對簡單的模式,簡單到很多時候都不認為它是一種設計模式。本文將詳細介紹迭代器模式

 

迭代器實現

  迭代器模式無非就是循環訪問聚合對象中的各個元素。比如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 < l; i++ ){
        callback.call( ary[i], i, ary[ i ] ); // 把下標和元素當作參數傳給callback 函數
    }
};

each( [ 1, 2, 3 ], function( i, n ){
    alert ( [ i, n ] );
});

 

迭代器分類

  迭代器可以分為內部迭代器和外部迭代器,它們有各自的適用場景

【內部迭代器】

  剛剛編寫的each函數屬于內部迭代器,each函數的內部已經定義好了迭代規則,它完全接手整個迭代過程,外部只需要一次初始調用

  內部迭代器在調用的時候非常方便,外界不用關心迭代器內部的實現,跟迭代器的交互也僅僅是一次初始調用,但這也剛好是內部迭代器的缺點。由于內部迭代器的迭代規則已經被提前規定,上面的each函數就無法同時迭代2個數組了

  比如現在有個需求,要判斷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 不相等' );
        }
    });
    alert ( 'ary1 和ary2 相等' );
};
compare( [ 1, 2, 3 ], [ 1, 2, 4 ] ); // throw new 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
    }
};

  下面來改寫compare函數:

var compare = function( iterator1, iterator2 ){
    while( !iterator1.isDone() && !iterator2.isDone() ){
        if ( iterator1.getCurrItem() !== iterator2.getCurrItem() ){
            throw new Error ( 'iterator1 和iterator2 不相等' );
        }
        iterator1.next();
        iterator2.next();
    }
    alert ( 'iterator1 和iterator2 相等' );
}
var iterator1 = Iterator( [ 1, 2, 3 ] );
var iterator2 = Iterator( [ 1, 2, 3 ] );
compare( iterator1, iterator2 ); // 輸出: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 < length; i++ ) {
            value = callback.call( obj[ i ], i, obj[ i ] );
            if ( value === false ) {
                break;
            }
        }
    } else {
        for ( i in obj ) { // 迭代object 對象
            value = callback.call( obj[ i ], i, obj[ i ] );
            if ( value === false ) {
                break;
            }
        }
    }
    return obj;
};

 

倒序迭代器

  迭代器模式提供了循環訪問一個聚合對象中每個元素的方法,但它沒有規定以順序、倒序還是中序來循環遍歷聚合對象

  下面實現一個倒序訪問的迭代器

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 ); // 分別輸出:2, 1 ,0
});

 

中止迭代器

  迭代器可以像普通for循環中的break一樣,提供一種跳出循環的方法。在jQuery的each函數里有這樣一句:

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

  這句代碼的意思是,約定如果回調函數的執行結果返回false,則提前終止循環。下面把之前的each函數改寫一下:

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
});

 

文件上傳

  下面是一段關于文件上傳的代碼,目的是根據不同的瀏覽器獲取相應的上傳組件對象:

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') );
    }
  }
};

  在不同的瀏覽器環境下,選擇的上傳方式是不一樣的。因為使用瀏覽器的上傳控件進行上傳速度快,可以暫停和續傳,所以首先會優先使用控件上傳。如果瀏覽器沒有安裝上傳控件,則使用Flash上傳,如果連Flash也沒安裝,那就只好使用瀏覽器原生的表單上傳了

  上面的代碼為了得到一個upload對象,getUploadObj函數里面充斥了try,catch以及if條件分支。缺點顯而易見,很難閱讀,且嚴重違反開閉原則。在開發和調試過程中,需要來回切換不同的上傳方式,如果增加了一些另外的上傳方式,比如,HTML5上傳,這時唯一的辦法是繼續往getUploadObj函數里增加條件分支

  目前一共有3種可能的上傳方式,但不知道目前正在使用的瀏覽器支持哪幾種。把每種獲取upload對象的方法都封裝在各自的函數里,然后使用一個迭代器,迭代獲取這些upload對象,直到獲取到一個可用的為止

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

var getFlashUploadObj = function(){
  if ( supportFlash() ){    // supportFlash 函數未提供
    var str = '<object type="application/x-shockwave-flash"></object>'; 
    return $( str ).appendTo( $('body') );
  }
  return false;
};

var getFormUpladObj = function(){
  var str = '<input name="file" type="file" class="ui-file"/>'; // 表單上傳
  return $( str ).appendTo( $('body') );
};

  在getActiveUploadObj、getFlashUploadObj、getFormUpladObj這3個函數中都有同一個約定:如果該函數里面的upload對象是可用的,則讓函數返回該對象,反之返回false,提示迭代器繼續往后面進行迭代

  所以我們的迭代器只需進行下面這兩步工作:1、提供一個可以被迭代的方法,使得getActiveUploadObj,getFlashUploadObj以及getFlashUploadObj依照優先級被循環迭代;2、如果正在被迭代的函數返回一個對象,則表示找到了正確的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, getFormUpladObj );

  重構代碼之后,獲取不同上傳對象的方法被隔離在各自的函數里互不干擾,try、catch和if分支不再糾纏在一起,使得可以很方便地的維護和擴展代碼。比如,給上傳項目增加了Webkit控件上傳和HTML5上傳,要做的僅僅是下面一些工作

var getWebkitUploadObj=function(){
  //具體代碼略
};

var getHtml5UploadObj=function(){
  //具體代碼略
};

  依照優先級把它們添加進迭代器:

var uploadObj=iteratorUploadObj(getActiveUploadObj,getWebkitUploadObj,getFlashUploadObj,getHtml5UploadObj,getFormUpladObj);

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()