16種JavaScript設計模式(中)

ni742017發表於2018-12-09

簡介

上文中介紹了學習設計模式前需要了解的一些基礎概念和js的基礎模式-原型模式,沒看過的同學可以點這裡,本章將介紹以下幾種模式

  • 單例模式
  • 策略模式
  • 代理模式
  • 迭代器模式
  • 釋出訂閱模式
  • 命令模式
  • 組合模式

單例模式

定義:保證一個類只有一個例項,並提供一個訪問他的全域性訪問點

簡介:單例模式是一種常用的模式,我們在多次引入其他模組時,並不需要每次都建立一個新的模組物件,複用之前建立過的物件不僅能減少記憶體的開銷,同時也可以體驗共享物件帶來的便利。簡單來說就是使用閉包持久儲存函式上一次的執行結果,在之後的呼叫中直接返回。例如js 中模組載入的方式:require、import都使用到了該模式

例:

var getSingle = function (fn) { // 建立單例方法
    var result // 通過閉包儲存建立過的物件
    return function () {
        return result || (result = fn.apply(this, arguments))
    }
}

var createPerson = getSingle(function (name) {
    return {name: name}
})

var person1 = createPerson('張三')
var person2 = createPerson('李四')

console.log(person1, person2);  // {name: '張三'} {name: '張三'}
複製程式碼

進階示例:dom彈窗

策略模式

定義:定義一系列演算法,把他們一個個封裝起來,並且可以相互替換

簡介:現實生活中當我們要達成一個目的的時候通常會有多種方案可以選擇。比如馬上年底要發年終獎了,公司針對不同型別的員工有不同的獎金策略,對於表現突出的員工發3個月的工資,一般的員工發2個月的,打醬油的發1個月的,專寫Bug的扣一個月。在這裡 發年終獎 是目的,針對表現不同的員工我們有多種發獎金的策略,可以使用策略模式來實現

例:

var strategies = { // 針對不同表現的員工定製策略,每個策略接受同型別的引數返回相同的結果
    S(salary) {
        return salary * 3
    },
    A(salary) {
        return salary * 2
    },
    B(salary) {
        return salary
    },
    C(salary) {
        return -salary
    }
}

var calculateBonus = function (salary, strategy) {
    return strategies[strategy](salary)
}

console.log(calculateBonus(10000, 'S')); // 30000
console.log(calculateBonus(1000, 'C')); // -1000 

複製程式碼

進階示例:表單校驗

代理模式

定義:當直接訪問一個物件不方便或者不滿足需要時,為其提供一個替身物件來控制對這個物件的訪問

簡介:代理模式是一種非常有意義的模式,在我們日常開發中有許多常用功能都可以通過代理模式實現的,例如 防抖動函式(debounce 常用於控制使用者輸入後回撥函式觸發的時機),節流函式(throttle 常用於控制resize、scroll等事件的觸發頻率),下面我們實現一個簡單的節流函式

例:

var throttle = function (fn, interval) {
    var firstTime, timer
    return function () {
        var _this = this
        if(!firstTime) {
            fn.apply(this, arguments)
            firstTime = true
        }
        
        if (!timer) {
            timer = setTimeout(function() {
                fn.apply(_this, arguments)
                timer = null
            }, interval);
        }
    }
}

var onScroll = function () {
    console.log('onScroll', Date.now())
}
var throttleOnScroll = throttle(onScroll, 2000)

setInterval(throttleOnScroll, 300) // 每2秒執行一次onScroll函式
複製程式碼

進階示例:圖片預載入

迭代器模式

定義:提供一種方法順序訪問一個聚合物件中的各個元素,而要不需要暴露該物件的內部表示

簡介:迭代器模式簡單來說就是將迭代過程從業務邏輯中抽離,簡化開發,其分為內迭代和外迭代。目前許多語言都已經內建了迭代器的實現,如ES5中的forEach函式就是一種內迭代的實現。

例:

Array.prototype.myEach = function (cb) {
    for (let index = 0; index < this.length; index++) {
        const element = this[index];
        if(cb(element, index) === false) {
            break
        }
        
    }
};

['a','b','c'].myEach(console.log) // a b c
複製程式碼

進階示例:外迭代

釋出訂閱模式(劃重點)

定義:分離事件建立者和執行者,執行方只需訂閱感興趣的事件發生點。減少物件間的耦合關係,新的訂閱者出現時不必修改原有程式碼邏輯

簡介:釋出訂閱模式又叫觀察者模式,它定義了物件間一種一對多的關係,當一個物件的狀態發生改變時,所有依賴於它的物件都將得到通知。 釋出訂閱模式在我們日常開發中應用十分廣泛,如瀏覽器的dom事件通知機制(document.addEventListener),以及vue框架中資料改變時自動重新整理dom的雙向繫結機制都是基於該模式

例:

var Event = function () {
    var clientList = {} // 訂閱者陣列

    this.listen = function (key, cb) { // 訂閱方法
        clientList[key] = clientList[key] || []
        clientList[key].push(cb)
    }

    this.remove = function (key, cb) { // 取消訂閱
        var fns = clientList[key]
        if(!cb) {
            clientList[key] = []
        }else if(fns && fns.length) {
            clientList[key] = fns.filter(fn => fn !== cb)
        }
    }

    this.trigger = function () { // 通知訂閱者
        var key = Array.prototype.shift.call(arguments)
        var args = arguments
        var fns = clientList[key]
        var _this = this

        if(fns && fns.length) {
            fns.myEach(function(fn) {
                fn.apply(_this, args)
            })
        }
    }
}

var event = new Event()

event.listen('phone', function getPhone() {
    Array.prototype.unshift.call(arguments, '有個挨千刀的半夜打電話來了他是:')
    console.log.apply(this, arguments)
})

event.trigger('phone', '大狗子') // 有個挨千刀的半夜打電話來了他是:大狗子
event.trigger('phone', '二狗子') // 有個挨千刀的半夜打電話來了他是:二狗子
複製程式碼

進階示例:進階版釋出訂閱模式

命令模式

定義:將一組行為抽象為對像並提供執行、撤銷等方法,解決它與呼叫者的之間的耦合關係

簡介:命令模式是對簡單優雅的模式之一,其中“命令”指的是一個執行某些特定事情的指令。該模式適用於需要向某些物件發出請求,但不知道接受者是誰,也不知道要執行哪些操作。例如我們平時去飯店點菜是我們並不需要知道這道菜是誰做的怎麼做的,我們只需要請服務員把需求寫在訂單上就可以了。

例:

var client = { // 顧客(命令發出者)
    name: '鐵蛋兒'
}
var cook = { // 廚師(命令發執行者)
    makeFood: function (food) {
        console.log('開始做:', food)
    },
    serveFood: function (client) {
        console.log('上菜給:', client.name)
    }    
}

function OrderCommand(receiver, food) { // 命令物件
    this.receiver = receiver
    this.food = food
}

OrderCommand.prototype.execute = function (cook) { // 提供執行方法
    cook.makeFood(this.food)
    cook.serveFood(this.receiver)
}

var command = new OrderCommand(client, '宮保雞丁')
command.execute(cook) // 開始做:宮保雞丁; 上菜給鐵蛋兒

複製程式碼

進階示例:控制小球運動

組合模式

定義:將一系列具有相同方法的物件合併成一個具有該方法的組合物件,統一執行

簡介:組合模式將物件組合成樹形結構,以表示“部分-整體”的層次結構。同時利用物件的多型性,使得單個物件的使用和組合物件的使用具有一致性。例如我們通過命令模式定義了一系列的命令,並且希望組合這些命令形成一個命令巨集統一的執行。

例:

// 定義一些命令
var openDoorCommand = {
    execute: function(){
        console.log('開門')
    }
}

var openPcCommand = {
    execute: function(){
        console.log('開電腦')
    }
}

var openLolCommand = {
    execute: function(){
        console.log('擼一局')
    }
}

// 定義命令巨集組合命令
var MarcoCommand = {
    list: [],
    add: function (command) {
        this.list.push(command)
    },
    execute: function () {
        this.list.forEach(function(command) {
            command.execute()
        })
    }
}

MarcoCommand.add(openDoorCommand)
MarcoCommand.add(openPcCommand)
MarcoCommand.add(openLolCommand)
MarcoCommand.execute() // 開門 開電腦 擼一局

複製程式碼

進階示例:組合命令控制小球運動

感悟

相信看到這裡的同學會發現其實我們平時編寫的程式碼中已經多多少少用到了一些設計模式,他們並不是一些高深複雜的理論知識。掌握一些常用的設計模式在幫助我們提升自己的程式碼質量的同時也能幫我們更好的和同事溝通。

總結

本文中所有進階示例都可以在github.com/ni742015/de…這裡找到,這些示例結合了我們實際開發中會遇到的一些問題(可以說是這篇文章的精華了),能加深你對設計模式的理解,希望對大家有幫助。

系列連結

  1. 16種JavaScript設計模式(上)
  2. 16種JavaScript設計模式(中)
  3. 16種JavaScript設計模式(下)還在計劃中。

本文主要參考了《javascript設計模式與開發實踐》一書

相關文章