《前端實戰總結》之迭代器模式的N+1種應用場景

petterchx發表於2021-09-09

眼看12月就來了,抓住今年的尾巴,好好總結一下前端的不足與收穫。這篇文章是筆者寫設計模式專題的第二篇文章,也是基於工作中的總結和提煉,在實際應用場景中都會大量使用,至於為什麼要寫設計模式,主要是為了提高團隊程式碼質量和可維護性,後續會繼續推出設計模式相關的文章,供大家參考和學習。

你將學到

  • 迭代器模式的含義
  • 實現一個陣列迭代器
  • 實現一個物件迭代器
  • 實現路徑查詢/賦值迭代器
  • 如何用迭代器的思想解決分支迴圈巢狀問題
  • 實現一個圖片播放器

正文

1.迭代器的含義

迭代器模式主要的思想就是在不暴露物件內部結構的同時可以按照一定順序訪問物件內部的元素。

其實javascript中的很多方法都運用了迭代器的思想,比如陣列的forEach,every,find,some,map,entries等等,這些操作極大的簡化了我們的邏輯操作,接下來我們就來看看它的具體應用吧。

2.實現一個陣列迭代器

我們都知道javascript中陣列的forEach方法,那麼不用這個方法,我們能自己實現一個嗎?

// 陣列迭代器
let eachArr = function(arr, fn) {
    let i = 0,
    len = arr.length;
    for(; i < len; i++) {
        if(fn.call(arr[i], i, arr[i]) === false) {
            break;
        }
    }
}

// 使用
eachArr([1,2,3,4], (index, value) => { console.log(index, value) })

3.實現一個物件迭代器

物件迭代器和陣列迭代器類似, 只是傳參不同,如下:

// 物件迭代器
let eachObj = function(obj, fn) {
    for(let key in obj) {
        if(fn.call(obj[key], key, obj[key]) === false) {
            break;
        }
    }
}

// 使用
eachObj({a: 11, b: 12}, (key, value) => { console.log(key, value) })

4.實現路徑查詢/賦值迭代器

有時候我們操作物件的某些屬性時,我們不知道伺服器端是否將該屬性或者該屬性的上級屬性正確的返回給我們,這個時候我們直接透過點語法或者[]語法直接訪問會導致程式碼報錯,因此需要我們每一層操作都要做安全校驗,這樣會產生大量臃腫程式碼,比如:

let obj = {};
// 獲取 obj.num.titNum
let titNum = obj.num.titNum;    // 報錯
let titNum = obj && obj.num && obj.num.titNum;   // 正確

我們透過迭代器可以極大的減少這種校驗,實現更健壯的程式碼模式:

let findObjAttr = function(obj, key){
    if(!obj || !key) {
        return undefined
    }
    let result = obj;
    key = key.split('.');
    for(let i =0; len = key.length; i< len; i++) {
        if(result[key[i]] !== undefined) {
            result = result[key[i]]
        }else {
            return undefined
        }
    }
    return result
}
// 使用
let a = { b: { c: { d: 1 } } };
findObjAttr(a, 'a.b.c.d')     // 1

這種方式是不是有點類似於lodash的物件/陣列查詢器呢?同理,我們也可以實現路徑賦值器,如下所示:

let setObjAttr = function(obj, key, value){
    if(!obj) {
        return false
    }
    let result = obj,
    key = key.split('.');
    for(let i =0, len = key.length; i< len - 1; i++){
        if(result[key[i]] === undefined) {
            result[key[i]] = {};
        }
        
        if(!(result[key[i]] instanceof Object)){
            // 如果第i層對應的不是一個物件,則剖出錯誤
            throw new Error('is not Object')
            return false
        }
        
        result = result[key[i]]
    }
    return result[key[i]] = val
}

// 使用
setObjAttr(obj, 'a.b.c.d', 'xuxi')

5.如何用迭代器的思想解決分支迴圈巢狀問題

分支迴圈巢狀的問題主要是指在迴圈體中還需要進行額外的判斷,如果判斷條件變多,將會造成嚴重的效能開銷問題,如下面的例子:

// 資料分組
function group(name, num) {
    let data = [];
    for(let i = 0; i < num; i++){
        switch(name) {
            case 'header':
               data[i][0] = 0;
               data[i][1] = 1;
               break;
           case 'content':
               data[i][0] = 2;
               data[i][1] = 3;
               break;
           case 'footer':
               data[i][0] = 4;
               data[i][1] = 532;
               break;
           default:
               break;
        }
    }
    return data
}

由以上分析可知,上面的程式碼還有很多最佳化空間,因為每一次遍歷都要進行一次分支判斷,那麼如果num變成100000,且name的種類有100種,那麼我們就要做100000*100種無用的分支判斷,這樣無疑會讓你的程式碼在大資料下卡死。不過我們可以透過以下這種方式最佳化它:

// 資料分組
function group(name, num) {
    let data = [];
    let strategy = function() {
        let deal = {
            'default': function(i){
                return
            },
            'header': function(i){
               data[i][0] = 0;
               data[i][1] = 1;
            },
           'content': function(i){
               data[i][0] = 2;
               data[i][1] = 3;
            }, 
            //...
        }
        return function(name) {
            return deal[name] || deal['default']
        }
    }();
    // 迭代器處理資料
    function _each(fn) {
       for(let i = 0; i < num; i++){
        fn(i)
       }
    }
    
    _each(strategy(name))
    
    return data
}

這樣我們就能避免分支判斷,極大的提高了程式碼效率和效能。

6.實現一個圖片播放器

圖片描述
圖片播放器主要有以上幾個功能,上一頁,下一頁,首頁,尾頁,自動播放按鈕,停止按鈕。具體元件的設計機構可以參考我寫的demo:

// 圖片播放器
let imgPlayer = function(imgData, box) {
    let container = box && document.querySelector(box) || document,
    img = container.querySelector('img'),
    // 獲取圖片長度
    len = imgData.length,
    // 當前索引值
    index = 0;
    // 初始化圖片
    img.src = imgData[0];

    var timer = null;

    return {
        // 獲取第一個圖片
        first: function() {
            index = 0
            img.src = imgData[index]
        },
        // 獲取最後一個圖片
        last: function() {
            index = len - 1
            img.src = imgData[index]
        },
        // 切換到前一張圖片
        pre: function() {
            if(--index > 0) {
                img.src = imgData[index]
            }else {
                index = 0
                img.src = imgData[index]
            }
        },
        // 切換到後一張圖片
        next: function() {
            if(++index < len) {
                img.src = imgData[index]
            }else {
                index = len - 1
                img.src = imgData[index]
            }
        },
        // 自動播放圖片
        play: function() {
            timer = setInterval(() => {
                if(index > len - 1) {
                    index = 0
                }
                img.src = imgData[index]
                index++
            }, 5000)
        },
        // 停止播放圖片
        stop: function() {
            clearInterval(timer)
        }
    }
}

// 使用
let player = new imgPlayer(imgData, '#box')

總之,迭代器思想和其他設計模式的組合,可以設計出各種各樣高度配置的元件,所以說學好並理解javascript設計模式的精髓,決定了我們的高度和態度。

最後

如果想了解更多webpack,node,gulp,css3,javascript,nodeJS,canvas等前端知識和實戰,歡迎在《趣談前端》一起學習討論,共同探索前端的邊界。

更多推薦

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4606/viewspace-2824390/,如需轉載,請註明出處,否則將追究法律責任。

相關文章