發佈訂閱模式

一、什麼是發佈訂閱模式?ajax

發佈訂閱模式,在咱們生活中是很是常見的一種,好比咱們常見的微信公衆號訂閱號,被訂閱的公衆號做者會將更新的文章發送給每一個訂閱者,再好比咱們找中介買房子,告訴了中介咱們的需求(訂閱),而後中介手上有了適合的房源後,將信息發送給全部訂閱的人(發佈)等。緩存

二、看一個最簡單發佈訂閱的代碼例子微信

let e = {
    arr:[],
    on(fn){
        this.arr.push(fn);
    },
    emit(){
        this.arr.forEach(fn=>fn());
    }
}
e.on(()=>{
   console.log("haha") 
})
e.on(()=>{
   console.log("是否是傻") 
})
function sendMsg(){
    e.emit()
}
sendMsg()

咱們定義了一個對象e,它有兩個方法,on:用於接收訂閱的事件,將事件存儲在變量arr中,emit:用於遍歷全部訂閱的事件,並執行app

而後在調用方法sendMsg()的時候,執行e.emit()方法。異步

還有前面咱們使用after函數讀取文件的例子,同時去讀取文件,當兩個文件讀取完成以後,須要打印出兩個文件的內容,這個也可使用咱們的發佈訂閱來完成。代碼以下:ide

const e = {
    arr:[],
    on(fn){
        this.arr.push(fn);
    },
    emit(){
        this.arr.forEach(fn=>fn());
    }
}

let info = {};

e.on(()=>{console.log("我讀完了")})
e.on(()=>{
    if(Object.keys(info).length==2){
        console.log(info);
    }
});

fs = require("fs");
fs.readFile("name.txt","utf8",(err,data)=>{
    info["name"]=data;
    e.emit();
})
fs.readFile("age.txt","utf8",(err,data)=>{
    info["age"]=data;
    e.emit();
})

上面這個例子中,我先訂閱了兩個方法,一個函數做用是輸出我讀完了,一個函數用於判斷當前存儲文件內容的對象是否有兩項,有的話,說明已經讀完了。而後讓fs分別去讀取各自的文件name.txt和age.txt,每次讀取完成都調用發佈函數e.emit(),而後去輪流執行前面訂閱的兩個方法函數

三、發佈訂閱模式的優勢ui

1)發佈訂閱模式最大的優勢就是,解耦。this

  發佈訂閱模式能夠取代對象之間的硬編碼的通知機制。一個對象不用再顯示的調用另外一個對象的某個接口。且當有新的訂閱者出現時,發佈者不用修改任何代碼,一樣發佈者發生改變時,以前的訂閱者也不會受到影響。編碼

  舉一個生活中的實例,好比我和朋友一塊兒開了一個店,僱傭了一個店員幫咱們看店,而後咱們想要了解店裏的經營狀況,因此但願店員每成交一個訂單就給咱們分別發一條短信通知。

  要實現這個功能,咱們須要分三步走:

    a)須要定義一個發佈者,如例子中的店員

    b)發佈者須要一個緩存列表,用於存放回調函數,以便通知訂閱者

    c)發佈者發佈消息的時候,須要遍歷緩存列表,依次觸發訂閱函數

const employee = {
    employer:[], // 用於存放我和朋友的回調函數
    listen(fn){
        this.employer.push(fn)
    },
    trigger(...args){
        this.employer.forEach(fn=>fn(...args))
    }
}

employee.listen((salesAmount)=>{
    console.log("我:",salesAmount);  // 我:800
})
employee.listen((salesAmount)=>{
    console.log("朋友: ", salesAmount);  // 朋友:800
})

employee.trigger(800);

上面就實現了一個簡單的發佈訂閱,可是過了一段時間後,店裏生意變得愈來愈好,天天的訂單愈來愈多,我和朋友天天會收到不少不少短信,因此但願將模式修改一下,我和朋友決定分工合做,我收取全部訂單金額大於200的訂單信息,朋友接受全部訂單金額小於200的訂單信息。要實現這個功能,咱們須要在訂閱的時候,傳遞一個參數key,說明咱們的需求,而後店員在發佈的時候,也傳遞對應的key進行區分,代碼以下:

const employee = {
    employer:[],  // 存放訂閱信息的緩存列表
    listen(key, fn){  // 訂閱消息
        if(this.employer.indexOf(key)==-1){
            this.employer[key] = [];  // 若是尚未訂閱過此類信息,給此類信息建立一個緩存列表
        }
        this.employer[key].push(fn);  // 訂閱的消息,添加到緩存列表中
    },
    trigger(){  // 發佈消息
        let key = Array.prototype.shift.call(arguments);  // 取出消息類型 lt200;gt200
        let fns = this.employer[key]  // 取出對應的方法
        if(!fns || fns.length==0){  
            return;
        }
        fns.forEach(fn=>{
            fn(arguments);  // 執行訂閱方法(這裏的arguments是已經去掉了消息類型的剩餘參數)
        })
    }
}
employee.listen("lt200",(salesAmount)=>{  // 訂閱
    console.log("朋友: ",salesAmount);
})
employee.listen("gt200",(salesAmount)=>{
    console.log("我:",salesAmount);
})
employee.trigger("lt200", 80);  // 發佈
employee.trigger("gt200",600);

 這樣,我和朋友就能夠按照本身的須要分別收到信息了。可是有一天朋友以爲,每天收那麼多信息,太麻煩了,不想再收到信息了,怎麼辦呢?聰明的你確定想到了,既然有訂閱,就應該有取消訂閱啊,因此接下來,咱們須要給對象添加一個取消訂閱的方法

employee.cancelListen = function(key, fn){     // 取消訂閱
    let func = this.employer[key];
    if(!func){    // 說明對應的key沒有人訂閱
        return false;
    }
    if(!fn){    // 取消訂閱的人沒有傳遞須要取消哪一個訂閱,則將當前key下對應的全部訂閱所有取消
        fn.length = 0;
    }
   for(let l=func.length-1;i>=0;l--){
     if(fn==func[l]){
       func.splice(l,1)
     }
   }
}
function a(...args){
  console.log("我",...args)
}
employee.listen("gt200",a)
console.log(employee.trigger("gt200",900))  // 我900
employee.cancelListen("gt200",a)
console.log(employee.trigger("gt200", 900))  // 此時沒有輸出,由於訂閱已經被取消了

 可是咱們的程序裏面常常會有這種狀況,因爲ajax異步,可能在程序尚未訂閱的時候,發佈程序已經執行了,因此咱們須要先將發佈事件暫存起來,等到有人來訂閱的時候,再執行。

const event = {    // 訂閱
    cache:{},
    stackFn:{},
    listen(key, fn){
        if(!cache[key]){
            cache[key] = [];
        }
        cache[key].push(fn);
        let stackArgs_ = stackFn[key];
        if(!stackArgs_){
            return;
        }
        stackArgs.forEach(args,=>{
            fn(...args);
        })
    },
    trigger(){
        let key = Array.prototype.shift.call(arguments);// 取出第一個參數
        let fns = this.cache[key];
        if(!fns || fns.length==0){
            if(!this.stackFn[key]){
                this.stackFn[key] = [];
            }
            this.stackFn[key].push(arguments);
            return;
        }
        fns.forEach(fn_=>{
            fn_(...arguments)
        })
    }
}

完整代碼以下:

  1 const event = {
  2     let Event, _default = "default";
  3     Event = function(){
  4         let each, _create, _remove, _listen, _trigger, namespaceCache, _shift=Array.prototype.shift;
  5         each = function(ary, fn){
  6             ary.forEach(item=>{
  7                 fn.call(item);
  8             });    
  9         }
 10         _create = function(key, fn, cache){
 11             // 訂閱
 12             if(!cache[key]){
 13                 cache[key] = [];
 14             }
 15             cache[key].push(fn);
 16         }
 17         
 18         _trigger = function(){
 19             let key = _shift(arguments);
 20             let args = _shift(arguments);
 21             let cache = _shift(arguments);
 22             let self = this;
 23             fns = cache[key];
 24             if(!fns || fns.length==0){
 25                 return;
 26             }
 27             each(fns, function(){
 28                 this.apply(self, args);
 29             })    // 至關於 fns.forEach(fn=>{fn(...args)})
 30     
 31         _remove = function(key, fn, cache){
 32             // 取消訂閱
 33             let fns = cache[key];
 34             if(!fns || fns.length==0){
 35                 return;
 36             }
 37             if(!fn){
 38                 fns = [];
 39             }
 40             for(let i=0;i<fns.length;i++){
 41                 if(fns[i]==fn){
 42                     fns.shift(i,1);
 43                 }
 44             }
 45         }
 46         _create = function(namespace){
 47             // 建立訂閱
 48             namespace = namespace||_default;
 49             let ret, cache={}, offlineCache={};
 50             ret = {
 51                 listen:(key, fn)=>{
 52                     // 建立訂閱:1)調用event的訂閱方法,執行訂閱操做;2)因爲程序的異步,可能會出現執行發佈的時候,訂閱還沒有完成,因此須要在訂閱的時候,查看是否有離線信息,若是有的話,執行離線信息
 53                     _listen(key, fn, cache);
 54                     offlineFn = offlineCache[key];
 55                     if(!offlineFn){
 56                         return
 57                     }
 58                     each(offlineFn, function(){
 59                         this();
 60                     })    // 至關於offlineFn.forEach(fn=>{fn(key, fn, cache)})
 61                     offlineCache[key]= [];// 離線信息執行一次以後,就清除
 62                 },
 63                 remove:(key, fn)=>{
 64                     // 取消訂閱,直接調用event的取消訂閱便可
 65                     _remove(key, fn, cache);
 66                 },
 67                 trigger:(key, ...args)=>{
 68                     // 發佈,調用event的_trigger
 69                     _trigger(key, args, cache);
 70                     // 判斷是否有訂閱,若是沒有訂閱,須要將它加入離線緩存中
 71                     let stack = cache[key]
 72                     if(stack && stack.length>0){
 73                         return;
 74                     }
 75                     let fn = function(){
 76                         return _trigger.apply(self, [key, args, cache]);
 77                     }
 78                     if(offlineCache && offlineCache[key].length==0){
 79                         offlineCache[key] = [];
 80                     }
 81                     offlineCache[key].push(fn);
 82                 }
 83             }
 84             return namespace?(namespaceCache[namespace]?namespaceCache[namespace]:namespaceCache[namespace]=ret):ret;
 85         }
 86         return {
 87             create:_create,
 88             listen:function(obj, fn){
 89                 let event = this.create(obj['namespace']);
 90                 event.listen(obj["key"], fn);
 91             },
 92             remove:function(obj, fn){
 93                 let event = this.create(obj['namespace']);
 94                 event.remove(obj['key'], fn);
 95             }
 96             trigger:function(obj, ...args){
 97                 let event = this.create(obj["namespace"]);
 98                 event.trigger.apply(this, obj[key], ...args);
 99             }
100         }
101     }();
102     return Event;
103 })();
View Code
相關文章
相關標籤/搜索