釋出-訂閱模式

小小坤發表於2017-09-18

簡介

釋出-訂閱模式又叫觀察者模式,它定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都將得到通知。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都少不了釋出-訂閱模式的參與。然而釋出-訂閱模式也存在一些缺點,建立訂閱本身會消耗一定的時間與記憶體,也許當你訂閱一個訊息之後,之後可能就不會發生。釋出-訂閱模式雖然它弱化了物件與物件之間的關係,但是如果過度使用,物件與物件的必要聯絡就會被深埋,會導致程式難以跟蹤與維護。

相關文章