前言
疊代器模式應該算是比較 “低調” 的設計模式了,因為這種模式在日常工作中經常遇到,但由于本身這種模式的思想十分簡單,是以一般不會特别的去關注他。
疊代器模式注重的是如何去疊代集合中每一個元素,大部分語言都内置了實作疊代器接口的資料結構,并且暴露出對應的方法供我們使用。
JavaScript中的疊代器模式展現
- 常用的數組就是一個可以疊代的對象,它提供了一個forEach方法可以讓我們快速使用一個回調來處理數組中的每一個元素,但是不用關心如何具體疊代的細節。這裡我們簡單實作一個類似的each。
const each = (target, callback) => {
for (let i = 0, l = target.length; i < l;i++) {
callback.call(this,target[i],i);
}
}
var a = ["foo","bar"];
each(a,(item, index) => {
console.log(item, index);
})
// foo 0
// bar 1
調用方并不關心疊代器内部的具體實作,我們隻要關注自己回調函數的實作即可。
值得一提的是,上述疊代器不僅僅對于數組有效,對于這樣的對象也可以使用。
var obj = {
0: 'foo',
1: 'bar',
length:2
};
each(obj,(item,index) => {
console.log(item, index)
})
// foo 0
// bar 1
這個可被疊代的對象需要滿足兩個特性。
- 擁有length屬性
- 可以通過下标通路。
實作一個外部疊代器
上面的例子中,疊代器的疊代邏輯是封裝在内部的,他做了這樣的事情:周遊傳入的對象,将傳入的回調作用在每一個對象中的元素,但是有時候我們想加入自己的一些業務邏輯,此時我們要實作一個簡單的外部疊代器。
假如我們有這樣的一個需求:
周遊某個對象數組,假如有某個對象的danger屬性為true,則停止周遊。看一下這個Demo.
class Iterator{
constructor(iterator){
if (!(this instanceof Iterator)) throw new Error('must be used with new operator')
if (iterator.length === undefined) throw new Error('the iterator must have "length" propoty')
this.iterator = iterator;
this.current = 0;
}
next(){
this.current++;
}
getCurrent(){
return this.iterator[this.current];
}
isDone(){
return this.current >= this.iterator.length;
}
}
function iteratorFactory(iterator){
return new Iterator(iterator);
}
我定義了一個Iterator類,并且申明了一個工廠方法。下面看看怎麼使用它。
var target = [
{name:'foo', danger:false},
{name:'bar', danger:true},
{name:'tee', danger:false},
{name:'nnk', danger:false},
];
const it= iteratorFactory(target);
while(!it.isDone()) {
const ret = it.getCurrent();
if (ret.danger) return;
console.log(ret)
it.next();
}
// { name: 'foo', danger: false }
我們在外部使用while循環,通過isDone控制結束條件,如果目前周遊的元素的danger屬性為true則直接停止循環。否則輸出該元素。
這樣一來疊代器的邏輯就由我們自己來控制了。注意這裡我們同樣可以傳入一個可被疊代的對象
應用場景
接下舉例一下我工作中用到疊代器模式的真實場景
在跨端開發中,有時候會遇到依賴運作環境執行的代碼,這裡可能是H5(泛指PC端的頁面和M站的頁面),android,ios。原先是這麼實作的
try {
if (weex.util.isWeb()) {
doWebCallback()
} else if (weex.util.isAndroid) {
doAndroidCallback()
} else if (weex.util.isIos) {
doIosCallback()
}
} catch(e) {
doDefaultCallback();
}
以上代碼邏輯有很多if else 分支,并且放在了try catch 中,功能上沒有什麼問題,但是耦合嚴重,假如現在我要針對PC和Mobile做區分處理,不得不回到這裡去修改if else 分支。又或者兜底邏輯變了,我又要去catch裡面修改對應邏輯。
這裡我們借用上面的外部疊代器來優化下這個操作。
webFn = () => {
if (!weex.util.isWeb()) return false;
return doWebCallback;
}
androidFn = () => {
if (!weex.util.isAndroid()) return false;
return doAndroidCallback;
}
iosFn = () => {
if (!weex.util.isIos()) return false;
return doIosCallback;
}
defaultFn = () => {
return doDefaultCallback;
}
const Fns = [webFn,androidFn,iosFn]
Fns.push(defaultFn);
const it= iteratorFactory(Fns);
while(!it.isDone()) {
const fn = it.getCurrent();
if (fn && typeof fn === "function") {
return fn();
}
it.next();
}
以後如果再加其他的分支邏輯,隻要寫一個按照以上标準的新方法,然後加入到Fns數組中即可。維護也友善。