迭代器模式是一種相對(duì)簡(jiǎn)單的模式,簡(jiǎn)單到很多時(shí)候我們都不認(rèn)為它是一種設(shè)計(jì)模式。目前的絕大部分語(yǔ)言都內(nèi)置了迭代器。
比如:JavaScript 的 Array.prototype.forEach
jQuery里一個(gè)非常有名的迭代器就是 $.each 方法,通過each我們可以傳入額外的function,然后來對(duì)所有的item項(xiàng)進(jìn)行迭代操作,例如:
$.each( [1, 2, 3], function( i, n ){
console.log( '當(dāng)前下標(biāo)為: '+ i,'當(dāng)前值為:' + n );
});
// 下標(biāo): 0 當(dāng)前值:1
// 下標(biāo): 1 當(dāng)前值:2
// 下標(biāo): 2 當(dāng)前值:3
定義
迭代器模式(Iterator):提供一種方法順序一個(gè)聚合對(duì)象中各個(gè)元素,而又不暴露該對(duì)象內(nèi)部表示。
迭代器的幾個(gè)特點(diǎn)是:
- 訪問一個(gè)聚合對(duì)象的內(nèi)容而無需暴露它的內(nèi)部表示。
- 為遍歷不同的集合結(jié)構(gòu)提供一個(gè)統(tǒng)一的接口,從而支持同樣的算法在不同的集合結(jié)構(gòu)上進(jìn)行操作。
- 遍歷的同時(shí)更改迭代器所在的集合結(jié)構(gòu)可能會(huì)導(dǎo)致問題(比如C#的foreach里不允許修改item)。
使用實(shí)例
實(shí)現(xiàn)自己的迭代器
參照jQuery的$.each 方法, 我們來自己實(shí)現(xiàn)一個(gè) each 函數(shù),each 函數(shù)接受 2 個(gè)參數(shù),第一個(gè)為被循環(huán)的數(shù)組,第二個(gè)為循環(huán)中的每一步后將被觸發(fā)的回調(diào)函數(shù):
var each = function( ary, callback ) {
for ( var i = 0, l = ary.length; i < l; i++ ){
callback.call( ary[i], i, ary[ i ] );
// 把下標(biāo)和元素當(dāng)作參數(shù)傳給callback 函數(shù)
}
};
each( [ "a", "b", "c" ], function( i, n ){
console.log( '自定義下標(biāo)為: '+ i,'自定義值為:' + n );
});
// 自定義下標(biāo)為: 0 自定義值為:a
// 自定義下標(biāo)為: 1 自定義值為:b
// 自定義下標(biāo)為: 2 自定義值為:c
迭代器模式的應(yīng)用舉例
以常用的上傳文件功能為例,在不同的瀏覽器環(huán)境下,選擇的上傳方式是不一樣的。因?yàn)槭褂脼g覽器的上傳控件進(jìn)行上傳速度快,可以暫停和續(xù)傳,所以我們首先會(huì)優(yōu)先使用控件上傳。如果瀏覽器沒有安裝上傳控件, 則使用 Flash 上傳, 如果連 Flash 也沒安裝,那就只好使用瀏覽器原生的表單上傳了,代碼為:
var getUploadObj = function() {
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上傳控件
} catch (e) {
if (supportFlash()) { // supportFlash 函數(shù)未提供
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'));
}
}
};
看看上面的代碼,為了得到一個(gè) upload 對(duì)象,這個(gè) getUploadObj 函數(shù)里面充斥了 try,catch 以及 if 條件分支。缺點(diǎn)是顯而易見的。
現(xiàn)在來梳理一下問題,目前一共有 3 種可能的上傳方式,我們不知道目前瀏覽器支持那種上傳方式,那就需要逐個(gè)嘗試,直到成功為止,分別定義以下幾個(gè)函數(shù):
// IE 上傳控件
var getActiveUploadObj = function() {
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload");
} catch (e) {
return false;
}
};
// Flash 上傳
var getFlashUploadObj = function() {
if (supportFlash()) { // supportFlash 函數(shù)未提供
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'));
};
在以上三個(gè)函數(shù)中,如果該函數(shù)里面的 upload 對(duì)象是可用的,則讓函數(shù)返回該對(duì)象,反之返回 false,提示迭代器繼續(xù)往后面進(jìn)行迭代。
那么迭代器只需進(jìn)行下面這幾步工作:
- 提供一個(gè)可以被迭代的方法,使得 getActiveUploadObj,getFlashUploadObj 以及 getFlashUploadObj 依照優(yōu)先級(jí)被循環(huán)迭代。
- 如果正在被迭代的函數(shù)返回一個(gè)對(duì)象,則表示找到了正確的 upload 對(duì)象,反之如果該函數(shù)返回 false,則讓迭代器繼續(xù)工作。
var iteratorUpload = function() {
for (var i = 0, fn; fn = arguments[i++];) {
var upload = fn();
if (upload !== false) {
return upload;
}
}
};
var uploadObj = iteratorUpload( getActiveUploadObj, getFlashUploadObj, getFormUpladObj )
重構(gòu)代碼之后,可以看到,上傳對(duì)象的各個(gè)函數(shù)彼此分離互補(bǔ)干擾,很方便維護(hù)和擴(kuò)展,如果后期增加了 Webkit 控件上傳和 HTML5 上傳,我們就增加對(duì)應(yīng)的函數(shù)功能并在迭代器里添加:
var getWebkitUploadObj = function(){
// 具體代碼略
}
var getHtml5UploadObj = function(){
// 具體代碼略
}
// 依照優(yōu)先級(jí)把它們添加進(jìn)迭代器
var uploadObj = iteratorUploadObj( getActiveUploadObj, getWebkitUploadObj,
getFlashUploadObj, getHtml5UploadObj, getFormUpladObj );
總結(jié)
迭代器的使用場(chǎng)景是:對(duì)于集合內(nèi)部結(jié)果常常變化各異,我們不想暴露其內(nèi)部結(jié)構(gòu)的話,但又想讓客戶代碼透明地訪問其中的元素,這種情況下我們可以使用迭代器模式。
參考引用資料
《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)踐》