簡介
釋出-訂閱模式又叫觀察者模式,它定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都將得到通知。JavaScript開發中我們一般用事件模型來代替傳統的釋出-訂閱模式
案例介紹1
小明最近喜歡上吃老北京燒餅,可是到了賣燒餅的地方發現已經賣完了,而且排隊的人還很多.幸運的是賣燒餅那個MM看小明長得帥,告訴小明等一會就有燒餅吃啦!可是小明現在還有約會要去,不知道燒餅能什麼時候出鍋,總不能因為吃燒餅而不去約會吧!這時候小明靈機一動,說燒餅MM把你電話給我吧!我先去忙,等會打電話問你燒餅好了沒有。燒餅MM也沒想太多,把電話給小明瞭。後來小龍也來買燒餅,情況跟小明差不多,小龍也把燒餅MM的電話要走了。可是問題就這來了,小明、小龍一會兒打一個電話給燒餅MM,導致燒餅MM很煩,辭職走了不幹了。
通過上邊的事情我們可以發現,存在好多問題
第一:賣燒餅的MM應該充當釋出者
第二:小明小龍的電話應該儲存在賣燒餅的使用者列表中,如果賣燒餅的MM離職,這使用者就會丟失
第三:實際上沒有這麼笨蛋的銷售方式的
賣燒餅的店主可以把小明、小龍的電話記錄下來,等店裡有燒餅了在通知小龍小明來拿這就是所謂的釋出-訂閱模式,程式碼如下:
/*燒餅店*/
var Sesamecakeshop={
clienlist:[],//快取列表
addlisten:function(fn){//增加訂閱者
this.clienlist.push(fn);
},
trigger:function(){//釋出訊息
for(var i=0,fn;fn=this.clienlist[i++];){
fn.apply(this,arguments);
}
}
}
/*小明發布訂閱*/
Sesamecakeshop.addlisten(function(price,taste){
console.log("小明發布的"+price+"元,"+taste+"味道的");
});
/*小龍釋出訂閱*/
Sesamecakeshop.addlisten(function(price,taste){
console.log("小龍釋出的"+price+"元,"+taste+"味道的");
});
Sesamecakeshop.trigger(10,"椒鹽");複製程式碼
從程式碼中可以看出,只有小明,小龍預定了燒餅,燒餅店就可以釋出訊息告訴小龍與小明。但是有個問題不知道大家發現了沒有。小明只喜歡椒鹽味道的。而小龍只喜歡焦糖味道的。上面的程式碼就滿足不了客戶的需求,給客戶一種感覺就是,不管我喜歡不喜歡,你都會發給我。如果釋出比較多,客戶就會感到厭煩,甚至會想刪除訂閱。下邊是對程式碼進行改良大家可以看看。
/*燒餅店*/
var Sesamecakeshop={
clienlist:{},/*快取列表*/
/**
* 增加訂閱者
* @key {String} 型別
* @fn {Function} 回掉函式
* */
addlisten:function(key,fn){
if(!this.clienlist[key]){
this.clienlist[key]=[];
}
this.clienlist[key].push(fn);
},
/**
* 釋出訊息
* */
trigger:function(){
var key=[].shift.call(arguments),//取出訊息型別
fns=this.clienlist[key];//取出該型別的對應的訊息集合
if(!fns || fns.length===0){
return false;
}
for(var i=0,fn;fn=fns[i++];){
fn.apply(this,arguments);
}
},
/**
* 刪除訂閱
* @key {String} 型別
* @fn {Function} 回掉函式
* */
remove:function(key,fn){
var fns=this.clienlist[key];//取出該型別的對應的訊息集合
if(!fns){//如果對應的key沒有訂閱直接返回
return false;
}
if(!fn){//如果沒有傳入具體的回掉,則表示需要取消所有訂閱
fns && (fns.length=0);
}else{
for(var l=fns.length-1;l>=0;l--){//遍歷回掉函式列表
if(fn===fns[l]){
fns.splice(l,1);//刪除訂閱者的回掉
}
}
}
}
}
/*小明發布訂閱*/
Sesamecakeshop.addlisten("焦糖",fn1=function(price,taste){
console.log("小明發布的"+price+"元,"+taste+"味道的");
});
/*小龍釋出訂閱*/
Sesamecakeshop.addlisten("椒鹽",function(price,taste){
console.log("小龍釋出的"+price+"元,"+taste+"味道的");
});
Sesamecakeshop.trigger("椒鹽",10,"椒鹽");
Sesamecakeshop.remove("焦糖",fn1);//注意這裡是按照地址引用的。如果傳入匿名函式則刪除不了
Sesamecakeshop.trigger("焦糖",40,"焦糖");複製程式碼
刪除的時候需要注意的是,如果訂閱的時候傳遞的是匿名函式,刪除的時候如果傳入的也是匿名函式。則刪除不了。因為刪除時候是按照地址引用刪除的。傳進去的兩個匿名函式,對應的地址引用是不同的。
案例介紹2
比如我們們常見的使用者身份分別有不同的功能,超級管理員擁有最高許可權,可以刪除修改任意使用者。而普通使用者則只能修改自己的賬戶資訊。首先是使用者身份驗證,驗證通過之後對應功能才可以顯示。
//登入釋出-訂閱模式
login={
clienlist:{},/*快取列表*/
/**
* 增加訂閱者
* @key {String} 型別
* @fn {Function} 回掉函式
* */
addlisten:function(key,fn){
if(!this.clienlist[key]){
this.clienlist[key]=[];
}
this.clienlist[key].push(fn);
},
/**
* 釋出訊息
* */
trigger:function(){
var key=[].shift.call(arguments),//取出訊息型別
fns=this.clienlist[key];//取出該型別的對應的訊息集合
if(!fns || fns.length===0){
return false;
}
for(var i=0,fn;fn=fns[i++];){
fn.apply(this,arguments);
}
}
}
//超級管理員修改所有使用者
var editall=(function(){
login.addlisten("loginsucc",function(data){
editall.setview(data);
});
return{
setview:function(data){
console.log(data);
console.log("超級管理員修改所有使用者");
}
}
})();
//僅僅修改自己
var editOwn=(function(){
login.addlisten("loginsucc",function(data){
editOwn.setview(data);
});
return{
setview:function(data){
console.log(data);
console.log("僅僅修改自己");
}
}
})();複製程式碼
釋出-訂閱模式簡單封裝
var _Event=(function(){
var clienlist={},
addlisten,trigger,remove;
/**
* 增加訂閱者
* @key {String} 型別
* @fn {Function} 回掉函式
* */
addlisten=function(key,fn){
if(!clienlist[key]){
clienlist[key]=[];
}
clienlist[key].push(fn);
};
/**
* 釋出訊息
* */
trigger=function(){
var key=[].shift.call(arguments),//取出訊息型別
fns=clienlist[key];//取出該型別的對應的訊息集合
if(!fns || fns.length===0){
return false;
}
for(var i=0,fn;fn=fns[i++];){
fn.apply(this,arguments);
}
};
/**
* 刪除訂閱
* @key {String} 型別
* @fn {Function} 回掉函式
* */
remove=function(key,fn){
var fns=clienlist[key];//取出該型別的對應的訊息集合
if(!fns){//如果對應的key沒有訂閱直接返回
return false;
}
if(!fn){//如果沒有傳入具體的回掉,則表示需要取消所有訂閱
fns && (fns.length=0);
}else{
for(var l=fns.length-1;l>=0;l--){//遍歷回掉函式列表
if(fn===fns[l]){
fns.splice(l,1);//刪除訂閱者的回掉
}
}
}
};
return{
addlisten:addlisten,
trigger:trigger,
remove:remove
}
})();
_Event.addlisten("jianbing",function(d,all){
console.log("釋出的訊息來自:"+d+",具體資訊:"+all);
});
_Event.addlisten("jianbing",function(d,all){
console.log("釋出的訊息來自:"+d+",具體資訊:"+all);
})
_Event.trigger("jianbing","小小坤","前端工程師,擅長JavaScript,喜歡結交更多的前端技術人員,歡迎喜歡技術的你加QQ群:198303871")複製程式碼
總結
釋出-訂閱模式就是常說的觀察者模式,在實際開發中非常有用。它的優點是為時間是解耦,為物件之間解構,它的應用非常廣泛,既可以在非同步程式設計中也可以幫助我們完成更鬆的解耦。釋出-訂閱模式還可以幫助我們實現設計模式,從架構上來看,無論MVC還是MVVC都少不了釋出-訂閱模式的參與。然而釋出-訂閱模式也存在一些缺點,建立訂閱本身會消耗一定的時間與記憶體,也許當你訂閱一個訊息之後,之後可能就不會發生。釋出-訂閱模式雖然它弱化了物件與物件之間的關係,但是如果過度使用,物件與物件的必要聯絡就會被深埋,會導致程式難以跟蹤與維護。