JavaScript 設計模式 --- 迭代器模式

前言

迭代器模式應該算是比較 “低調” 的設計模式了,因爲這種模式在日常工作中經常遇到,但由於本身這種模式的思想十分簡單,所以一般不會特別的去關注他。
迭代器模式注重的是如何去迭代集合中每一個元素,大部分語言都內置了實現迭代器接口的數據結構,並且暴露出對應的方法供我們使用。

JavaScript中的迭代器模式體現

  1. 常用的數組就是一個可以迭代的對象,它提供了一個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

這個可被迭代的對象需要滿足兩個特性。

  1. 擁有length屬性
  2. 可以通過下標訪問。

實現一個外部迭代器

上面的例子中,迭代器的迭代邏輯是封裝在內部的,他做了這樣的事情:遍歷傳入的對象,將傳入的回調作用在每一個對象中的元素,但是有時候我們想加入自己的一些業務邏輯,此時我們要實現一個簡單的外部迭代器。
假如我們有這樣的一個需求:
遍歷某個對象數組,假如有某個對象的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數組中即可。維護也方便。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章