概念
設計模式是怎么解決問題的一種方案
常見的設計模式
單例模式
概念:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
應用:項目封裝個websocket用于大屏,redux,vuex都應用了單例模式的思想;現在很多第三方庫都是單例模式,多次引用只會使用同一個對象,如jquery
、lodash
、moment
實現:先判斷實例存在與否,如果存在則直接返回,如果不存在就創建了再返回,這就確保了一個類只有一個實例對象
// 定義一個類
function Singleton(name) {this.name = name;this.instance = null;
}
// 原型擴展類的一個方法getName()
Singleton.prototype.getName = function() {console.log(this.name)
};
// 獲取類的實例
Singleton.getInstance = function(name) {if(!this.instance) {this.instance = new Singleton(name);}return this.instance
};// 獲取對象1
const a = Singleton.getInstance('a');
// 獲取對象2
const b = Singleton.getInstance('b');
// 進行比較
console.log(a === b);//true
工廠模式
概念:不暴露創建對象的具體邏輯,而是將邏輯封裝在一個函數中,那么這個函數就可以被視為一個工廠。
工廠模式根據抽象程度的不同可以分為:
-
簡單工廠模式(Simple Factory):用一個工廠對象創建同一類對象類的實例.
function Factory(career) {function User(career, work) {this.career = career this.work = work}let workswitch(career) {case 'coder':work = ['寫代碼', '修Bug'] return new User(career, work)breakcase 'boss':work = ['喝茶', '開會', '審批文件']return new User(career, work)break} } let coder = new Factory('coder') console.log(coder) let boss = new Factory('boss') console.log(boss)
-
工廠方法模式(Factory Method):工廠方法模式跟簡單工廠模式差不多,但是把具體的產品放到了工廠函數的prototype中.
// 工廠方法 function Factory(career){if(this instanceof Factory){var a = new this[career]();return a;}else{return new Factory(career);} } // 工廠方法函數的原型中設置所有對象的構造函數 Factory.prototype={'coder': function(){this.careerName = '程序員'this.work = ['寫代碼', '修Bug'] },'hr': function(){this.careerName = 'HR'this.work = ['招聘', '員工信息管理']} } let coder = new Factory('coder') console.log(coder) let hr = new Factory('hr') console.log(hr)
-
抽象工廠模式(Abstract Factory):簡單工廠模式和工廠方法模式都是直接生成實例,但是抽象工廠模式不同,抽象工廠模式并不直接生成實例, 而是用于對產品類簇的創建。
通俗點來講就是:簡單工廠和工廠方法模式的工作是生產產品,那么抽象工廠模式的工作就是生產工廠的
let CareerAbstractFactory = function(subType, superType) {// 判斷抽象工廠中是否有該抽象類if (typeof CareerAbstractFactory[superType] === 'function') {// 緩存類function F() {}// 繼承父類屬性和方法F.prototype = new CareerAbstractFactory[superType]()// 將子類的constructor指向父類subType.constructor = subType;// 子類原型繼承父類subType.prototype = new F()} else {throw new Error('抽象類不存在')} } //由于JavaScript中并沒有抽象類的概念,只能模擬,可以分成四部分: //用于創建抽象類的函數 //抽象類 //具體類 //實例化具體類 //上面代碼中CareerAbstractFactory就是一個抽象工廠方法,該方法在參數中傳遞子類和父類,在方法體內部實現了子類對父類的繼承
工廠模式適用場景如下:
- 如果你不想讓某個子系統與較大的那個對象之間形成強耦合,而是想運行時從許多子系統中進行挑選的話,那么工廠模式是一個理想的選擇
- 將new操作簡單封裝,遇到new的時候就應該考慮是否用工廠模式;
- 需要依賴具體環境創建不同實例,這些實例都有相同的行為,這時候我們可以使用工廠模式,簡化實現的過程,同時也可以減少每種對象所需的代碼量,有利于消除對象間的耦合,提供更大的靈活性
策略模式
概念:定義一系列的算法,把它們一個個封裝起來,目的就是將算法的使用與算法的實現分離開來。
一個基于策略模式的程序至少由兩部分組成:
策略類(可變),策略類封裝了具體的算法,并負責具體的計算過程
環境類(不變),接受客戶的請求,隨后將請求委托給某一個策略類
var obj = {"A": function(salary) {return salary * 4;},"B" : function(salary) {return salary * 3;},"C" : function(salary) {return salary * 2;}
};
var calculateBouns =function(level,salary) {return obj[level](salary);
};
console.log(calculateBouns('A',10000)); // 40000
策略模式的優點有如下:
- 策略模式利用組合,委托等技術和思想,有效的避免很多if條件語句
- 策略模式提供了開放-封閉原則,使代碼更容易理解和擴展
- 策略模式中的代碼可以復用
代理模式
概念:給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用。
在ES6
中,存在proxy
構建函數能夠讓我們輕松使用代理模式:
const proxy = new Proxy(target, handler);
而按照功能來劃分,javascript
代理模式常用的有:
-
緩存代理:緩存代理可以為一些開銷大的運算結果提供暫時的存儲,在下次運算時,如果傳遞進來的參數跟之前一致,則可以直接返回前面存儲的運算結果。
//緩存代理可以為一些開銷大的運算結果提供暫時的存儲,在下次運算時,如果傳遞進來的參數跟之前一致,則可以直接返回前面存儲的運算結果 var proxyMult = (function () {var cache = {};return function () {var args = Array.prototype.join.call(arguments, ",");if (args in cache) {return cache[args];}return (cache[args] = mult.apply(this, arguments));}; })();proxyMult(1, 2, 3, 4); // 輸出:24 proxyMult(1, 2, 3, 4); // 輸出:24
-
虛擬代理:虛擬代理把一些開銷很大的對象,延遲到真正需要它的時候才去創建。
常見的就是圖片預加載功能:
// 圖片本地對象,負責往頁面中創建一個img標簽,并且提供一個對外的setSrc接口 let myImage = (function(){let imgNode = document.createElement( 'img' );document.body.appendChild( imgNode );return {//setSrc接口,外界調用這個接口,便可以給該img標簽設置src屬性setSrc: function( src ){imgNode.src = src;}} })(); // 代理對象,負責圖片預加載功能 let proxyImage = (function(){// 創建一個Image對象,用于加載需要設置的圖片let img = new Image;img.onload = function(){// 監聽到圖片加載完成后,給被代理的圖片本地對象設置src為加載完成后的圖片myImage.setSrc( this.src );}return {setSrc: function( src ){// 設置圖片時,在圖片未被真正加載好時,以這張圖作為loading,提示用戶圖片正在加載myImage.setSrc( 'https://img.zcool.cn/community/01deed576019060000018c1bd2352d.gif' );img.src = src;}} })();proxyImage.setSrc( 'https://xxx.jpg' );
應用場景:
現在的很多前端框架或者狀態管理框架都使用代理模式,用與監聽變量的變化。
使用代理模式代理對象的訪問的方式,一般又被稱為攔截器,比如我們在項目中經常使用
Axios
的實例來進行 HTTP 的請求,使用攔截器interceptor
可以提前對 請求前的數據 服務器返回的數據進行一些預處理。以及上述應用到的緩存代理和虛擬代理。
中介者模式
? 通過一個中介者對象,其他所有的相關對象都通過該中介者對象來通信,當其中的一個對象發生改變時,只需要通知中介者對象即可。
? 通過中介者模式可以解除對象與對象之間的緊耦合關系
裝飾者模式
? 在原有方法維持不變,在原有方法上再掛載其他方法來滿足現有需求。
觀察者模式
? 觀察者模式定義了對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都將得到通知,并自動更新。
? 觀察者模式屬于行為型模式,行為型模式關注的是對象之間的通訊,觀察者模式就是觀察者和被觀察者之間的通訊。
// 被觀察者模式
class Subject {constructor() {this.observerList = [];}addObserver(observer) {this.observerList.push(observer);}removeObserver(observer) {const index = this.observerList.findIndex(o => o.name === observer.name);this.observerList.splice(index, 1);}notifyObservers(message) {const observers = this.observeList;observers.forEach(observer => observer.notified(message));}
}//觀察者
class Observer {constructor(name, subject) {this.name = name;if (subject) {subject.addObserver(this);}}notified(message) {console.log(this.name, 'got message', message);}
}
//使用
const subject = new Subject();
const observerA = new Observer('observerA', subject);
const observerB = new Observer('observerB');
subject.addObserver(observerB);
subject.notifyObservers('Hello from subject');
subject.removeObserver(observerA);
subject.notifyObservers('Hello again');
發布訂閱模式
? 發布-訂閱是一種消息范式,消息的發送者(稱為發布者)不會將消息直接發送給特定的接收者(稱為訂閱者)。而是將發布的消息分為不同的類別,無需了解哪些訂閱者(如果有的話)可能存在。
// 發布訂閱中心
class PubSub {constructor() {this.messages = {};this.listeners = {};}// 添加發布者publish(type, content) {const existContent = this.messages[type];if (!existContent) {this.messages[type] = [];}this.messages[type].push(content);}// 添加訂閱者subscribe(type, cb) {const existListener = this.listeners[type];if (!existListener) {this.listeners[type] = [];}this.listeners[type].push(cb);}// 通知notify(type) {const messages = this.messages[type];const subscribers = this.listeners[type] || [];subscribers.forEach((cb, index) => cb(messages[index]));}
}//發布者代碼
class Publisher {constructor(name, context) {this.name = name;this.context = context;}publish(type, content) {this.context.publish(type, content);}
}//訂閱者代碼
class Subscriber {constructor(name, context) {this.name = name;this.context = context;}subscribe(type, cb) {this.context.subscribe(type, cb);}
}//使用代碼
const TYPE_A = 'music';
const TYPE_B = 'movie';
const TYPE_C = 'novel';const pubsub = new PubSub();const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, 'we are young');
publisherA.publish(TYPE_B, 'the silicon valley');
const publisherB = new Publisher('publisherB', pubsub);
publisherB.publish(TYPE_A, 'stronger');
const publisherC = new Publisher('publisherC', pubsub);
publisherC.publish(TYPE_C, 'a brief history of time');const subscriberA = new Subscriber('subscriberA', pubsub);
subscriberA.subscribe(TYPE_A, res => {console.log('subscriberA received', res)
});
const subscriberB = new Subscriber('subscriberB', pubsub);
subscriberB.subscribe(TYPE_C, res => {console.log('subscriberB received', res)
});
const subscriberC = new Subscriber('subscriberC', pubsub);
subscriberC.subscribe(TYPE_B, res => {console.log('subscriberC received', res)
});pubsub.notify(TYPE_A);
pubsub.notify(TYPE_B);
pubsub.notify(TYPE_C);//發布者和訂閱者需要通過發布訂閱中心進行關聯,發布者的發布動作和訂閱者的訂閱動作相互獨立,無需關注對方,消息派發由發布訂閱中心負責。
發布訂閱、觀察者模式區別
- 在觀察者模式中,觀察者是知道Subject的,Subject一直保持對觀察者進行記錄。然而,在發布訂閱模式中,發布者和訂閱者不知道對方的存在。它們只有通過消息代理進行通信。
- 在發布訂閱模式中,組件是松散耦合的,正好和觀察者模式相反。
- 觀察者模式大多數時候是同步的,比如當事件觸發,Subject就會去調用觀察者的方法。而發布-訂閱模式大多數時候是異步的(使用消息隊列)