發布訂閱模式


1、什么是發布訂閱模式?

發布訂閱模式,在我們生活中是非常常見的一種,比如我們常見的微信公眾號訂閱號,被訂閱的公眾號作者會將更新的文章發送給每個訂閱者,再比如我們找中介買房子,告訴了中介我們的需求(訂閱),然后中介手上有了適合的房源后,將信息發送給所有訂閱的人(發布)等。

2、看一個最簡單發布訂閱的代碼例子

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:用於遍歷所有訂閱的事件,並執行

然后在調用方法sendMsg()的時候,執行e.emit()方法。

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

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(),然后去輪流執行前面訂閱的兩個方法

3、發布訂閱模式的優點

1)發布訂閱模式最大的優點就是,解耦。

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

  舉一個生活中的實例,比如我和朋友一起開了一個店,雇佣了一個店員幫我們看店,然后我們想要了解店里的經營情況,所以希望店員每成交一個訂單就給我們分別發一條短信通知。

  要實現這個功能,我們需要分三步走:

    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(!this.cache[key]){
            this.cache[key] = [];
        }
        this.cache[key].push(fn);
        let stackArgs_ = this.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)
        })
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM