簡(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)
代理模式
定義:當(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ù)加載
迭代器模式
定義:提供一種方法順序訪問(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)
組合模式
定義:將一系列具有相同方法的對(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ū)