[1]迭代器實現 [2]迭代器分類 [3]迭代類數組 [4]倒序迭代器 [5]中止迭代器 [6]文件上傳 ...
前面的話
迭代器模式是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不需要暴露該對象的內部表示。迭代器模式可以把迭代的過程從業務邏輯中分離出來,在使用迭代器模式之後,即使不關心對象的內部構造,也可以按順序訪問其中的每個元素。迭代器模式是一種相對簡單的模式,簡單到很多時候都不認為它是一種設計模式。本文將詳細介紹迭代器模式
迭代器實現
迭代器模式無非就是迴圈訪問聚合對象中的各個元素。比如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);