JavaScript中釋出/訂閱模式的理解

vi_young發表於2019-03-03

訂閱釋出模式的介紹

釋出訂閱模式,它定義了一種一對多的關係,可以使多個觀察者物件對一個主題物件進行監聽,當這個主題物件發生改變時,依賴的所有物件都會被通知到。

在生活中我們常常遇到這樣一種情況,我們在使用新聞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、雖然可以弱化物件之間的聯絡,如果過度使用的話,反而使程式碼不好理解及程式碼不好維護等等。

相關文章