1. 程式人生 > 其它 >js設計模式之釋出訂閱模式

js設計模式之釋出訂閱模式

1. 定義

釋出-訂閱模式其實是一種物件間一對多的依賴關係,當一個物件的狀態傳送改變時,所有依賴於它的物件都將得到狀態改變的通知。

訂閱者(Subscriber)把自己想訂閱的事件註冊(Subscribe)到排程中心(Event Channel),當釋出者(Publisher)釋出該事件(Publish Event)到排程中心,也就是該事件觸發時,由排程中心統一排程(Fire Event)訂閱者註冊到排程中心的處理程式碼。

2 釋出訂閱模式的圖解以及與觀察者模式之間的差異和類似之處:

3 實現釋出訂閱模式的 相關思路:

  • 建立一個物件
  • 在該物件上建立一個快取列表(排程中心)
  • on 方法用來把函式 fn 都加到快取列表中(訂閱者註冊事件到排程中心)
  • emit 方法取到 arguments 裡第一個當做 event,根據 event 值去執行對應快取列表中的函式(釋出者釋出事件到排程中心,排程中心處理程式碼)
  • off 方法可以根據 event 值取消訂閱(取消訂閱)
  • once 方法只監聽一次,呼叫完畢後刪除快取函式(訂閱一次)

eg1:簡單一點的demo:

// 定義一個物件作為排程中心,或者一個類。
let eventEmitter = {};

// 快取列表,存放 event 及 fn
eventEmitter.list = {};

// 訂閱
eventEmitter.on = function (event, fn) {
    let _this = this;
    // 如果物件中沒有對應的 event 值,也就是說明沒有訂閱過,就給 event 建立個快取列表
    // 如有物件中有相應的 event 值,把 fn 新增到對應 event 的快取列表裡
    (_this.list[event] || (_this.list[event] = [])).push(fn);
    return _this;
};

// 釋出
eventEmitter.emit = function () {
    let _this = this;
    // 第一個引數是對應的 event 值,直接用陣列的 shift 方法取出
    let event = [].shift.call(arguments), // 刪除並拿到arguments
的第一項 詳細參考:https://blog.csdn.net/u013946061/article/details/108269650部落格。 fns = [..._this.list[event]]; // 如果快取列表裡沒有 fn 就返回 false if (!fns || fns.length === 0) { return false; } // 遍歷 event 值對應的快取列表,依次執行 fn fns.forEach(fn => { fn.apply(_this, arguments); }); return _this; }; function user1 (content) { console.log('使用者1訂閱了:', content); }; function user2 (content) { console.log('使用者2訂閱了:', content); }; // 訂閱 eventEmitter.on('article', user1); eventEmitter.on('article', user2); // 釋出 eventEmitter.emit('article', 'Javascript 釋出-訂閱模式'); /* 使用者1訂閱了: Javascript 釋出-訂閱模式 使用者2訂閱了: Javascript 釋出-訂閱模式 */e

eg2: 實現一次訂閱的once方法和取消訂閱的off方法:

let eventEmitter = {
    // 快取列表
    list: {},
    // 訂閱
    on (event, fn) {
        let _this = this;
        // 如果物件中沒有對應的 event 值,也就是說明沒有訂閱過,就給 event 建立個快取列表
        // 如有物件中有相應的 event 值,把 fn 新增到對應 event 的快取列表裡
        (_this.list[event] || (_this.list[event] = [])).push(fn);
        return _this;
    },
    // 監聽一次 並且只執行一次 之後取消訂閱
    once (event, fn) {
        // 先繫結,呼叫後刪除
        let _this = this;
        function on () {
            _this.off(event, on); //取消訂閱
            fn.apply(_this, arguments); // 執行fn函式
        }
        on.fn = fn;
        _this.on(event, on);
        return _this;
    },
    // 取消訂閱
    off (event, fn) {
        let _this = this;
        let fns = _this.list[event];
        // 如果快取列表中沒有相應的 fn,返回false
        if (!fns) return false;
        if (!fn) {
            // 如果沒有傳 fn 的話,就會將 event 值對應快取列表中的 fn 都清空
            fns && (fns.length = 0);
        } else {
            // 若有 fn,遍歷快取列表,看看傳入的 fn 與哪個函式相同,如果相同就直接從快取列表中刪掉即可
            let cb;
            for (let i = 0, cbLen = fns.length; i < cbLen; i++) {
                cb = fns[i];
                if (cb === fn || cb.fn === fn) {
                    fns.splice(i, 1);
                    break
                }
            }
        }
        return _this;
    },
    // 釋出
    emit () {
        let _this = this;
        // 第一個引數是對應的 event 值,直接用陣列的 shift 方法取出
        let event = [].shift.call(arguments),
            fns = [..._this.list[event]];
        // 如果快取列表裡沒有 fn 就返回 false
        if (!fns || fns.length === 0) {
            return false;
        }
        // 遍歷 event 值對應的快取列表,依次執行 fn
        fns.forEach(fn => {
            fn.apply(_this, arguments);
        });
        return _this;
    }
};

function user1 (content) {
    console.log('使用者1訂閱了:', content);
}

function user2 (content) {
    console.log('使用者2訂閱了:', content);
}

function user3 (content) {
    console.log('使用者3訂閱了:', content);
}

function user4 (content) {
    console.log('使用者4訂閱了:', content);
}

// 訂閱
eventEmitter.on('article1', user1);
eventEmitter.on('article1', user2);
eventEmitter.on('article1', user3);

// 取消user2方法的訂閱
eventEmitter.off('article1', user2);

eventEmitter.once('article2', user4)

// 釋出
eventEmitter.emit('article1', 'Javascript 釋出-訂閱模式');
eventEmitter.emit('article1', 'Javascript 釋出-訂閱模式');
eventEmitter.emit('article2', 'Javascript 觀察者模式');
eventEmitter.emit('article2', 'Javascript 觀察者模式');

// eventEmitter.on('article1', user3).emit('article1', 'test111');

/*
    使用者1訂閱了: Javascript 釋出-訂閱模式
    使用者3訂閱了: Javascript 釋出-訂閱模式
    使用者1訂閱了: Javascript 釋出-訂閱模式
    使用者3訂閱了: Javascript 釋出-訂閱模式
    使用者4訂閱了: Javascript 觀察者模式
*/

  1 優點:

  • 物件之間解耦
  • 非同步程式設計中,可以更鬆耦合的程式碼編寫

  2 不足之處:

  • 建立訂閱者本身要消耗一定的時間和記憶體
  • 雖然可以弱化物件之間的聯絡,多個釋出者和訂閱者巢狀一起的時候,程式難以跟蹤維護

觀察者模式和釋出訂閱模式之間的區別和異同:

觀察者模式:觀察者(Observer)直接訂閱(Subscribe)主題(Subject),而當主題被啟用的時候,會觸發(Fire Event)觀察者裡的事件。

釋出訂閱模式:訂閱者(Subscriber)把自己想訂閱的事件註冊(Subscribe)到排程中心(Event Channel),當釋出者(Publisher)釋出該事件(Publish Event)到排程中心,也就是該事件觸發時,由排程中心統一排程(Fire Event)訂閱者註冊到排程中心的處理程式碼。

差異:

  • 在觀察者模式中,觀察者是知道 Subject 的,Subject 一直保持對觀察者進行記錄。然而,在釋出訂閱模式中,釋出者和訂閱者不知道對方的存在。它們只有通過訊息代理進行通訊。
  • 在釋出訂閱模式中,元件是鬆散耦合的,正好和觀察者模式相反。
  • 觀察者模式大多數時候是同步的,比如當事件觸發,Subject 就會去呼叫觀察者的方法。而釋出-訂閱模式大多數時候是非同步的(使用訊息佇列)。
  • 觀察者模式需要在單個應用程式地址空間中實現,而釋出-訂閱更像交叉應用模式。