JavaScript設計模式整理
一些常見模式實作:點我
什麼是設計模式?
一個模式就是一個可重用的方案,可應用于在軟體設計中的常見問題。另一種解釋就是一個我們如何解決問題的模闆 - 那些可以在許多不同的情況裡使用的模闆。
設計模式的分類:
建立型設計模式: 1、簡單工廠模式 2、工廠方法模式 3、抽象工廠模式 4、建造者模式 5、原型模式 6、單例模式
結構型設計模式: 7、外觀模式 8、擴充卡模式 9、代理模式 10、裝飾者模式 11、橋接模式 12、組合模式 13、享元模式
行為型設計模式: 14、模闆方法模式 15、觀察者模式 16、狀态模式 17、政策模式 18、職責鍊模式 19、指令模式 20、通路者模式 21、中介者模式 22、備忘錄模式 23、疊代器模式 24、解釋器模式
技巧型設計模式: 25、鍊模式 26、委托模式 27、資料通路對象模式 28、節流模式 29、簡單模闆模式 30、惰性模式 31、參與者模式 32、等待者模式
架構型設計模式: 33、同步子產品模式 34、異步子產品模式 35、Widget模式 36、MVC模式 37、MVP模式 38、MVVM模式
備注:該分類借鑒于《JavaScript設計模式-張容銘》
觀察者模式
所謂觀察者模式,其實就是為了實作松耦合(loosely coupled)。
觀察者模式指的是一個對象(Subject)維持一系列依賴于它的對象(Observer),當有關狀态發生變更時 Subject 對象則通知一系列 Observer 對象進行更新。
在觀察者模式中,Subject 對象擁有添加、删除和通知一系列 Observer 的方法等等,而 Observer 對象擁有更新方法等等。
在 Subject 對象添加了一系列 Observer 對象之後,Subject 對象則維持着這一系列 Observer 對象,當有關狀态發生變更時 Subject 對象則會通知這一系列 Observer 對象進行更新。
用《Head First設計模式》裡的氣象站為例子,每當氣象測量資料有更新,
方法就會被調用,于是我們可以在
changed()
changed()
方法裡面,更新氣象儀器上的資料,比如溫度、氣壓等等。
但是這樣寫有個問題,就是如果以後我們想在
方法被調用時,更新更多的資訊,比如說濕度,那就要去修改
changed()
changed()
方法的代碼,這就是緊耦合的壞處。
怎麼解決呢?使用觀察者模式,面向接口程式設計,實作松耦合。
觀察者模式裡面,
方法所在的執行個體對象,就是被觀察者(Subject,或者叫Observable),它隻需維護一套觀察者(Observer)的集合,這些Observer實作相同的接口,Subject隻需要知道,通知Observer時,需要調用哪個統一方法就好了:
changed()
(原文位址:https://juejin.im/post/6844903733738864654 )
function Subject(){
this.observers = [];
}
Subject.prototype = {
add:function(observer){ // 添加
this.observers.push(observer);
},
remove:function(observer){ // 删除
var observers = this.observers;
for(var i = 0;i < observers.length;i++){
if(observers[i] === observer){
observers.splice(i,1);
}
}
},
notify:function(){ // 通知
var observers = this.observers;
for(var i = 0;i < observers.length;i++){
observers[i].update();
}
}
}
function Observer(name){
this.name = name;
}
Observer.prototype = {
update:function(){ // 更新
console.log('my name is '+this.name);
}
}
var sub = new Subject();
var obs1 = new Observer('ttsy1');
var obs2 = new Observer('ttsy2');
sub.add(obs1);
sub.add(obs2);
sub.notify(); //my name is ttsy1、my name is ttsy2
上述代碼中,我們建立了 Subject 對象和兩個 Observer 對象,當有關狀态發生變更時則通過 Subject 對象的 notify 方法通知這兩個 Observer 對象,這兩個 Observer 對象通過 update 方法進行更新。
在 Subject 對象添加了一系列 Observer 對象之後,還可以通過 remove 方法移除某個 Observer 對象對它的依賴。
var sub = new Subject();
var obs1 = new Observer('ttsy1');
var obs2 = new Observer('ttsy2');
sub.add(obs1);
sub.add(obs2);
sub.remove(obs2);
sub.notify(); //my name is ttsy1
釋出訂閱模式
釋出訂閱模式指的是希望接收通知的對象(Subscriber)基于一個主題通過自定義事件訂閱主題,被激活事件的對象(Publisher)通過釋出主題事件的方式通知各個訂閱該主題的 Subscriber 對象。
function EventTarget(){
//EventTart類型有一個單獨的屬性handlers,用于存儲事件處理程式
this.handlers = {};
}
EventTarget.prototype = {
//重新指向EventTarget函數
constructor: EventTarget,
//type:事件類型 handler: 用于處理該事件的函數
addHandler:function(type,handler){
//判斷handlers對象中是否存在屬性名為type的屬性
if(typeof this.handlers[type] == "undefined"){
//如果不存在就将屬性名為type屬性的屬性值設定為一個新數組,用于存放type類型的事件處理程式
this.handlers[type] = [];
}
//将handler事件處理函數添加到數組末尾
this.handlers[type].push(handler);
},
//fire()函數要接受一個至少包含type屬性的對象作為參數
fire: function(event){
if(!event.target){
event.target = this;
}
//判斷要觸發的消息是否存在
if(this.handlers[event.type] instanceof Array){
//擷取對應類型的事件處理程式
var handlers = this.handlers[event.type];
for(var i=0,len = handlers.length; i<len; i++){
//調用每個函數,并給出event對象
handlers[i](event);
}
}
},
removeHandler: function(type, handler){
//防止删除的消息不存在的情況
if(this.handlers[type] instanceof Array){
var handlers = this.handlers[type];
for(var i=0,len = handlers.length;i<len;i++){
if(handlers[i] === handler){
break;
}
}
//将i位置的事件處理程式删除
handlers.splice(i,1);
}
}
};
EventTarget類型有一個單獨的屬性handlers,用于儲存事件處理程式。還有三個方法:
1. addHandler(),用于注冊給定類型事件的事件處理程式;
2. fire(),用于觸發一個事件;
3. removeHandler(),用于登出某個事件類型的事件處理程式。
addHandler()方法接受兩個參數: 事件類型和用于處理該事件的函數。當調用該方法時,會進行一次檢查,看看handlers屬性中是否已經存在一個針對該事件類型的數組;如果沒有,則建立一個新的。然後使用push()将該處理程式添加到數組的末尾。
如果要觸發一個事件,要調用fire()函數。該方法接受一個單獨的參數,是一個至少包含type屬性的對象。fire()方法給 event對象設定一個 target屬性,如果它尚未被指定的話。然後它就查找對應該事件類型的一組處理程式,調用各個函數,并給出event對象。因為這些都是自定義事件,是以 event 對象上還需要的額外資訊由你自己決定.
removeHandler()方法是 addhandler()的輔助,它們接受的參數一樣:事件的類型和事件處理程式。這個方法搜尋事件處理程式的數組找到要删除的處理程式的位置。如果找到了,則使用 break操作符退出for循環。然後使用splice方法将該項目從數組中删除.
然後使用EventTarget類型的自定義事件可以如下使用:
function handleMessage(event){
alert("message received:" + event.message);
}
//建立一個新對象
var target = new EventTarget();
//添加一個事件處理程式
target.addHandler("message",handleMessage);
//觸發事件
target.fire({type:"message",message:"Hello world!"});
//删除事件處理程式
target.removeHandler("message",handleMessage);
//再次觸發,應該沒有處理程式
target.fire({type:"message",message:"Hello world!"});
在這段代碼中,定義了 handleMessage()函數用于處理 message事件。它接受 event對象并輸出 message屬性。調用 target對象的 addHandler()方法并傳給" message以及 handleMessage()函數。在接下來的一行上,調用了fire()函數,并傳遞了包含2個屬性,即type和 message的對象直接量。它會調用 message事件的事件處理程式,這樣就會顯示一個警告框(來自 handleMessage())
然後删除了事件處理程式,這樣即使事件再次觸發,也不會顯示任何警告框。
因為這種功能是封裝在一種自定義類型中的,其他對象可以繼承 EventTarget并獲得這個行為,如下所示:
function Person(name , age){
EventTarget.call(this);
this.name = name;
this.age = age;
}
//組合繼承方法
// function inheritPrototype(subType,superType){
// var prototype = object(superType.prototype);
// prototype.constructor = subType;
// subType.prototype = prototype;
// }
inheritPrototype(Person,EventTargetart);
Person.prototype.say = function(message){
this.fire({type:"message",message:'message'});
};
Person類型使用了寄生組合繼承方法來繼承 EventTarget。一旦調用了say()方法,便觸發了事件,它包含了消息的細節。在某種類型的另外的方法中調用fire()方法是很常見的,同時它通常不是公開調用的。這段代碼可以照如下方式使用:
function handleMessage (event)(
alert (event. target name says
event message)i
//建立新 person
var person: new Person("Nicholas" 29)
//添加一個事件處理程式
person addHandler("message", handleMessage)
//在該對象上調用1個方法,它觸發消息事件
person say("Hi there.");
這個例子中的handleMessage()函數顯示了某人名字(通過 event.target.name獲得)的一個警告框和消息正文。當調用say()方法并傳遞一個消息時,就會觸發 message事件。接下來,它又會調用 handleMessage()函數并顯示警告框。
當代碼中存在多個部分在特定時刻互相互動的情況下,自定義事件就非常有用了。這時,如果每全對象都有對其他所有對象的引用,那麼整個代碼就會緊密耦合,同時維護也變得很困難,因為對某個對象的修改也會影響到其他對象,使用自定義事件有助于解耦相關對象,保持功能的隔絕。在很多情況中觸發事件的代碼和監聽事件的代碼是完全分離的。
在釋出訂閱模式裡,釋出者,并不會直接通知訂閱者,換句話說,釋出者和訂閱者,彼此互不相識。
互不相識?那他們之間如何交流?
答案是,通過第三者,也就是在消息隊列裡面,我們常說的經紀人Broker。
釋出者隻需告訴Broker,我要發的消息,topic是AAA;
訂閱者隻需告訴Broker,我要訂閱topic是AAA的消息;
于是,當Broker收到釋出者發過來消息,并且topic是AAA時,就會把消息推送給訂閱了topic是AAA的訂閱者。當然也有可能是訂閱者自己過來拉取,看具體實作。
也就是說,釋出訂閱模式裡,釋出者和訂閱者,不是松耦合,而是完全解耦的。
放一張極簡的圖,給大家對比一下這兩個模式的差別:
總結
從表面上看:往更深層次講:
- 觀察者模式裡,隻有兩個角色 —— 觀察者 + 被觀察者
- 而釋出訂閱模式裡,卻不僅僅隻有釋出者和訂閱者兩個角色,還有一個經常被我們忽略的 —— 經紀人Broker
從使用層面上講:
- 觀察者和被觀察者,是松耦合的關系
- 釋出者和訂閱者,則完全不存在耦合
- 觀察者模式,多用于單個應用内部
- 釋出訂閱模式,則更多的是一種跨應用的模式(cross-application pattern),比如我們常用的消息中間件
此部分參考:https://juejin.im/post/6844903733738864654