16種JavaScript設(shè)計(jì)模式(中)

簡(jiǎn)介

上文中介紹了學(xué)習(xí)設(shè)計(jì)模式前需要了解的一些基礎(chǔ)概念和js的基礎(chǔ)模式-原型模式芹敌,沒(méi)看過(guò)的同學(xué)可以點(diǎn)這里,本章將介紹以下幾種模式

  • 單例模式
  • 策略模式
  • 代理模式
  • 迭代器模式
  • 發(fā)布訂閱模式
  • 命令模式
  • 組合模式

單例模式

定義:保證一個(gè)類(lèi)只有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)他的全局訪問(wèn)點(diǎn)

簡(jiǎn)介:?jiǎn)卫J绞且环N常用的模式,我們?cè)诙啻我肫渌K時(shí),并不需要每次都創(chuàng)建一個(gè)新的模塊對(duì)象锯蛀,復(fù)用之前創(chuàng)建過(guò)的對(duì)象不僅能減少內(nèi)存的開(kāi)銷(xiāo),同時(shí)也可以體驗(yàn)共享對(duì)象帶來(lái)的便利次慢。簡(jiǎn)單來(lái)說(shuō)就是使用閉包持久保存函數(shù)上一次的執(zhí)行結(jié)果旁涤,在之后的調(diào)用中直接返回翔曲。例如js 中模塊加載的方式:require、import都使用到了該模式

例:

var getSingle = function (fn) { // 創(chuàng)建單例方法
    var result // 通過(guò)閉包保存創(chuàng)建過(guò)的對(duì)象
    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: '張三'}

進(jìn)階示例:dom彈窗

策略模式

定義:定義一系列算法劈愚,把他們一個(gè)個(gè)封裝起來(lái)瞳遍,并且可以相互替換

簡(jiǎn)介:現(xiàn)實(shí)生活中當(dāng)我們要達(dá)成一個(gè)目的的時(shí)候通常會(huì)有多種方案可以選擇。比如馬上年底要發(fā)年終獎(jiǎng)了造虎,公司針對(duì)不同類(lèi)型的員工有不同的獎(jiǎng)金策略傅蹂,對(duì)于表現(xiàn)突出的員工發(fā)3個(gè)月的工資,一般的員工發(fā)2個(gè)月的算凿,打醬油的發(fā)1個(gè)月的份蝴,專寫(xiě)B(tài)ug的扣一個(gè)月。在這里 發(fā)年終獎(jiǎng) 是目的氓轰,針對(duì)表現(xiàn)不同的員工我們有多種發(fā)獎(jiǎng)金的策略婚夫,可以使用策略模式來(lái)實(shí)現(xiàn)

例:

var strategies = { // 針對(duì)不同表現(xiàn)的員工定制策略,每個(gè)策略接受同類(lèi)型的參數(shù)返回相同的結(jié)果
    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 

進(jìn)階示例:表單校驗(yàn)

celue.gif

代理模式

定義:當(dāng)直接訪問(wèn)一個(gè)對(duì)象不方便或者不滿足需要時(shí)署鸡,為其提供一個(gè)替身對(duì)象來(lái)控制對(duì)這個(gè)對(duì)象的訪問(wèn)

簡(jiǎn)介:代理模式是一種非常有意義的模式案糙,在我們?nèi)粘i_(kāi)發(fā)中有許多常用功能都可以通過(guò)代理模式實(shí)現(xiàn)的,例如 防抖動(dòng)函數(shù)(debounce 常用于控制用戶輸入后回調(diào)函數(shù)觸發(fā)的時(shí)機(jī))靴庆,節(jié)流函數(shù)(throttle 常用于控制resize时捌、scroll等事件的觸發(fā)頻率),下面我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的節(jié)流函數(shù)

例:

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秒執(zhí)行一次onScroll函數(shù)

進(jìn)階示例:圖片預(yù)加載

agency.gif

迭代器模式

定義:提供一種方法順序訪問(wèn)一個(gè)聚合對(duì)象中的各個(gè)元素炉抒,而要不需要暴露該對(duì)象的內(nèi)部表示

簡(jiǎn)介:迭代器模式簡(jiǎn)單來(lái)說(shuō)就是將迭代過(guò)程從業(yè)務(wù)邏輯中抽離奢讨,簡(jiǎn)化開(kāi)發(fā),其分為內(nèi)迭代和外迭代焰薄。目前許多語(yǔ)言都已經(jīng)內(nèi)置了迭代器的實(shí)現(xiàn)拿诸,如ES5中的forEach函數(shù)就是一種內(nèi)迭代的實(shí)現(xiàn)。

例:

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

進(jìn)階示例:外迭代

發(fā)布訂閱模式(劃重點(diǎn))

定義:分離事件創(chuàng)建者和執(zhí)行者塞茅,執(zhí)行方只需訂閱感興趣的事件發(fā)生點(diǎn)亩码。減少對(duì)象間的耦合關(guān)系,新的訂閱者出現(xiàn)時(shí)不必修改原有代碼邏輯

簡(jiǎn)介:發(fā)布訂閱模式又叫觀察者模式野瘦,它定義了對(duì)象間一種一對(duì)多的關(guān)系描沟,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都將得到通知缅刽。
發(fā)布訂閱模式在我們?nèi)粘i_(kāi)發(fā)中應(yīng)用十分廣泛啊掏,如瀏覽器的dom事件通知機(jī)制(document.addEventListener),以及vue框架中數(shù)據(jù)改變時(shí)自動(dòng)刷新dom的雙向綁定機(jī)制都是基于該模式

例:

var Event = function () {
    var clientList = {} // 訂閱者數(shù)組

    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, '有個(gè)挨千刀的半夜打電話來(lái)了他是:')
    console.log.apply(this, arguments)
})

event.trigger('phone', '大狗子') // 有個(gè)挨千刀的半夜打電話來(lái)了他是:大狗子
event.trigger('phone', '二狗子') // 有個(gè)挨千刀的半夜打電話來(lái)了他是:二狗子

進(jìn)階示例:進(jìn)階版發(fā)布訂閱模式

命令模式

定義:將一組行為抽象為對(duì)像并提供執(zhí)行衰猛、撤銷(xiāo)等方法迟蜜,解決它與調(diào)用者的之間的耦合關(guān)系

簡(jiǎn)介:命令模式是對(duì)簡(jiǎn)單優(yōu)雅的模式之一,其中“命令”指的是一個(gè)執(zhí)行某些特定事情的指令啡省。該模式適用于需要向某些對(duì)象發(fā)出請(qǐng)求娜睛,但不知道接受者是誰(shuí)髓霞,也不知道要執(zhí)行哪些操作。例如我們平時(shí)去飯店點(diǎn)菜是我們并不需要知道這道菜是誰(shuí)做的怎么做的畦戒,我們只需要請(qǐng)服務(wù)員把需求寫(xiě)在訂單上就可以了方库。

例:

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

function OrderCommand(receiver, food) { // 命令對(duì)象
    this.receiver = receiver
    this.food = food
}

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

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

進(jìn)階示例:控制小球運(yùn)動(dòng)

command.gif

組合模式

定義:將一系列具有相同方法的對(duì)象合并成一個(gè)具有該方法的組合對(duì)象纵潦,統(tǒng)一執(zhí)行

簡(jiǎn)介:組合模式將對(duì)象組合成樹(shù)形結(jié)構(gòu),以表示“部分-整體”的層次結(jié)構(gòu)垃环。同時(shí)利用對(duì)象的多態(tài)性邀层,使得單個(gè)對(duì)象的使用和組合對(duì)象的使用具有一致性。例如我們通過(guò)命令模式定義了一系列的命令遂庄,并且希望組合這些命令形成一個(gè)命令宏統(tǒng)一的執(zhí)行寥院。

例:

// 定義一些命令
var openDoorCommand = {
    execute: function(){
        console.log('開(kāi)門(mén)')
    }
}

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

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() // 開(kāi)門(mén) 開(kāi)電腦 擼一局

進(jìn)階示例:組合命令控制小球運(yùn)動(dòng)

感悟

相信看到這里的同學(xué)會(huì)發(fā)現(xiàn)其實(shí)我們平時(shí)編寫(xiě)的代碼中已經(jīng)多多少少用到了一些設(shè)計(jì)模式,他們并不是一些高深復(fù)雜的理論知識(shí)涛目。掌握一些常用的設(shè)計(jì)模式在幫助我們提升自己的代碼質(zhì)量的同時(shí)也能幫我們更好的和同事溝通秸谢。

總結(jié)

本文中所有進(jìn)階示例都可以在https://github.com/ni742015/design-patterns這里找到,這些示例結(jié)合了我們實(shí)際開(kāi)發(fā)中會(huì)遇到的一些問(wèn)題(可以說(shuō)是這篇文章的精華了)霹肝,能加深你對(duì)設(shè)計(jì)模式的理解估蹄,希望對(duì)大家有幫助。

系列鏈接

1. 16種JavaScript設(shè)計(jì)模式(上)

2. 16種JavaScript設(shè)計(jì)模式(中)

3. 16種JavaScript設(shè)計(jì)模式(下)

本文主要參考了《javascript設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》一書(shū)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沫换,一起剝皮案震驚了整個(gè)濱河市元媚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苗沧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炭晒,死亡現(xiàn)場(chǎng)離奇詭異待逞,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)网严,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)识樱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人震束,你說(shuō)我怎么就攤上這事怜庸。” “怎么了垢村?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵割疾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我嘉栓,道長(zhǎng)宏榕,這世上最難降的妖魔是什么拓诸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮麻昼,結(jié)果婚禮上奠支,老公的妹妹穿的比我還像新娘。我一直安慰自己抚芦,他們只是感情好倍谜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著叉抡,像睡著了一般尔崔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卜壕,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天您旁,我揣著相機(jī)與錄音,去河邊找鬼轴捎。 笑死鹤盒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的侦副。 我是一名探鬼主播侦锯,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼秦驯!你這毒婦竟也來(lái)了尺碰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤译隘,失蹤者是張志新(化名)和其女友劉穎亲桥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體固耘,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡题篷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厅目。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片番枚。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖损敷,靈堂內(nèi)的尸體忽然破棺而出葫笼,到底是詐尸還是另有隱情,我是刑警寧澤拗馒,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布路星,位于F島的核電站,受9級(jí)特大地震影響诱桂,放射性物質(zhì)發(fā)生泄漏奥额。R本人自食惡果不足惜苫幢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望垫挨。 院中可真熱鬧韩肝,春花似錦、人聲如沸九榔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哲泊。三九已至剩蟀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間切威,已是汗流浹背育特。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留先朦,地道東北人缰冤。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像喳魏,于是被迫代替她去往敵國(guó)和親棉浸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348