訂閱釋出模式的介紹
釋出訂閱模式,它定義了一種一對多的關係,可以使多個觀察者物件對一個主題物件進行監聽,當這個主題物件發生改變時,依賴的所有物件都會被通知到。
在生活中我們常常遇到這樣一種情況,我們在使用新聞APP看新聞的時候,每個人喜歡的新聞型別各不一樣,比如我喜歡NBA,但是我們總不可能一天24小時在手機上一遍又一遍的重新整理,我們就會去新聞頻道中選擇NBA專欄來收藏,當勇士或者湖人有最新訊息,就會通知我們去觀看。
當然從上面的場景中是一個典型的釋出訂閱模式,APP的NBA專欄屬於釋出者,像我一樣廣大愛好籃球的小夥伴夢就屬於訂閱者,當一有最新的訊息,它們就會發布給我們。
實際用途
1.在jquery中很多地方都有釋出訂閱的蹤跡,例如事件中on和trigger中封裝的方法。
2.尤大大的Vue,中子父元件通訊使用的emit()和on()方法,使得元件得到解耦,開發更加高效。
如何實現訂閱釋出模式
1、首先想好誰是釋出者(比如上邊的APP的NBA專欄就是釋出者);
2、然後給釋出者新增一個快取列表,用於存放回撥函式來通知訂閱者(比如上面的我們球迷愛好者收藏了NBA專欄,相當於向釋出者注入了通知我們的函式);
3、最後就是釋出訊息,釋出者遍歷這個快取列表,依次觸發訂閱的函式。
表捉急,端起小板凳,先看一下這個簡單的釋出訂閱模式:
let NBAcol={};//自定義一個NBA專欄物件
NBAcol.list=[];// 這裡放一個列表用來快取訂閱者的回撥函式
NBAcol.on=function(fun){
this.list.push(fun); //把fn先存到列表中
};
//釋出事件
NBAcol.emit=function(){
this.list.forEach(cb => {
cb.apply(this, arguments);
});// 當釋出的時候再把列表裡存的函式依次執行
};
//小明的訂閱NBA專欄
NBAcol.on(function(team){
console.log("我訂閱的球隊是:"+team)
})
//小李的訂閱NBA專欄
NBAcol.on(function(team){
console.log("我訂閱的球隊是:"+team)
})
NBAcol.emit(`湖人`);
NBAcol.emit(`勇士`);
/*
我訂閱的球隊是:湖人;
我訂閱的球隊是:湖人;
我訂閱的球隊是:勇士;
我訂閱的球隊是:勇士;
*/
複製程式碼
上面就實現了一個簡單的訂閱釋出模式,不過從列印結果來看,有些尷尬,因為其實,小明只想訂閱湖人,小李要訂閱勇士。可是專欄都給他們推送了,顯然不太合理。之所以出現這種情況是因為在執行on方法的時候將訂閱函式列表中的函式依次都執行了。所以我們要對程式碼進行改造,我們可以先增加一個key,使訂閱者只訂閱自己感興趣的訊息。
let NBAcol={};//自定義一個NBA專欄物件
NBAcol.list={};// 這裡放一個列表用來快取訂閱者的回撥函式
NBAcol.on=function(key,fun){
// 如果還沒有訂閱過此類訊息,給該類訊息建立一個快取列表
if(!this.list[key]){
this.list[key]=[];
}
this.list[key].push(fun); //把fn先存到列表中
};
//釋出事件
NBAcol.emit=function(){
let key=Array.prototype.shift.call(arguments);// 取出訊息型別名稱
let funs=this.list[key];//匹配對應的回撥函式的結合
if(!funs||funs.length===0){//如果沒有訂閱過訊息,則return;
return;
};
funs.forEach(fun => {
fun.apply(this, arguments);
});// 當釋出的時候再把列表裡存的函式依次執行
};
//小明的訂閱NBA專欄
NBAcol.on(`xiaomin`,function(team){
console.log("我訂閱的球隊是:"+team)
})
//小李的訂閱NBA專欄
NBAcol.on(`xiaoli`,function(team){
console.log("我訂閱的球隊是:"+team)
})
NBAcol.emit(`xiaomin`,`湖人`);
NBAcol.emit(`xiaoli`,`勇士`);
/*
我訂閱的球隊是:湖人;
我訂閱的球隊是:勇士;
*/
複製程式碼
這樣子就可以啦,這個訂閱釋出的核心功能已經體現了。
如何取消事件的訂閱
比如上面的列子,假如我們訂閱了很多東西,不喜歡的時候我們要取消訂閱,該怎麼辦呢?看如下程式碼:
NBAcol.remove=function(key, fun) {
// 這回我們加入了取消訂閱的方法
let funs = this.list[key];
// 如果快取列表中沒有函式,返回false
if (!funs) return false;
// 如果沒有傳對應函式的話
// 就會將key值對應快取列表中的函式都清空掉
if (!fun) {
funs && (funs.length = 0);
} else {
// 遍歷快取列表,看看傳入的fun與哪個函式相同
// 如果相同就直接從快取列表中刪掉即可
funs.forEach((cb, i) => {
if (cb === fun) {
funs.splice(i, 1);
}
});
}
}
// 取消dog方法的訂閱
NBAcol.remove(`xiaoli`,function(team){
console.log("我訂閱的球隊是:"+team)
});
複製程式碼
這樣就可以取消訂閱啦,但是實際的開原始碼中,封裝遠比這要複雜,比如要考慮訂閱數量,還有多模組訂閱的封裝等等,所以在這裡我們還得在實際的業務模組中詳細考慮。
釋出訂閱模式的缺點:
當然一個任何一個東西都是有兩面性的,同樣釋出訂閱模式存在以下問題:
1、建立訂閱者需要消耗一定的時間和記憶體。
2、雖然可以弱化物件之間的聯絡,如果過度使用的話,反而使程式碼不好理解及程式碼不好維護等等。