簡單的寫一個釋出訂閱器

幽涯發表於2018-06-17

釋出-訂閱模式在開發中的應用其實是很廣泛的,比如大家都知道的 Vue,使用資料驅動模板的改變,將我們的雙手從繁瑣的 dom 操作中解放出來,稍微懂一些原理的同學們都知道,其雙向資料繫結就是通過資料劫持、釋出-訂閱和 dom 模板編譯實現的,就算不了解這個的同學,js 中的事件監聽相信做前端開發的同學都寫過;

其實事件監聽就是一個訂閱的操作,當某個事件觸發的時候,對該事件監聽時傳入的 callback 就會被執行,這就是一個完整的釋出的-訂閱流程;接下來我們就來簡單的寫一下一個釋出訂閱器:

思路:

俗話說:磨刀不誤砍柴工,在動手之前,縷清一下思路是有必要的,首先我們要明確這個釋出訂閱器的功能,其中包括

基本功能:

  • 可新增訂閱者
  • 可觸發訊息通知某個訂閱器執行其中的所有訂閱者
  • 可刪除指定訂閱者

升級功能:

  • 可同時給多個訂閱器新增訂閱者
  • 可刪除指定訂閱器中的所有訂閱者
  • 可指定同時刪除多個訂閱器或者直接清空所有訂閱器
  • 可新增一次性訂閱(該訂閱者一旦被執行,就會在訂閱器中移除)
  • 處理好 this 指向和實現鏈式呼叫
  • 新增錯誤判斷和提醒機制

接下來我們就分兩版上程式碼:

基礎版

/**
 * 釋出訂閱器建構函式
 */
var Publisher = (function() {
    function Publisher() {
        this._subs = {}; // 維護一個訂閱器列表
    }
    
    /**
     * 新增訂閱者
     * 若訂閱者需要插入的訂閱器不存在,則新建立一個
     * @param { string } type - 需要新增訂閱者的訂閱器名
     * @param { function } func - 訂閱者
     */
    Publisher.prototype.addSub = function(type, func) {
        if(!this._subs[type]) this._subs[type] = [];
        this._subs[type].push(func);
    };
    
    /**
     * 釋出通知
     * 通知指定訂閱器執行其中的每個訂閱者
     * @param { string } type - 需要通知其釋出訊息的訂閱器名
     */
    Publisher.prototype.notify = function(type) {
        if(!this._subs[type]) return;
        var args = Array.prototype.slice.call(arguments, 1);
        this._subs[type].forEach(function(item) {
            item.apply(this, args);
        }, this);
    };
    
    /**
     * 刪除訂閱者
     * @param { string } type - 指定操作的訂閱器名
     * @param { function } func - 指定需要刪除的訂閱者
     */
    Publisher.prototype.destory = function(type, func) {
        this._subs[type].forEach(function(item, index, array) {
            (item === func) && array.splice(index, 1);
        }, this);
    };
    
    return Publisher;
}());
複製程式碼

基礎版就如上面的程式碼,以最簡單的方式實現一個釋出訂閱器的基本功能(能訂閱,並能接收訊息,還能指定刪除)

升級版:

/**
 * 釋出訂閱器建構函式
 */
var Publisher = (function() {
    function Publisher() {
        this._subs = {};
    }
    
    /**
     * 新增訂閱者
     * 若訂閱者需要插入的訂閱器不存在,則新建立一個
     * 若傳入的訂閱器名為一個陣列,則遍歷陣列內的每個訂閱器新增訂閱者
     * @param { string|Array<string> } type - 需要新增訂閱者的訂閱器名
     * @param { function } func - 訂閱者
     * @returns { object } - 釋出器例項,可實現鏈式呼叫
     */
    Publisher.prototype.addSub = function(type, func) {
        if(Array.isArray(type)) {
            type.forEach(function(item) {
                this.addSub(item, func);
            }, this);
        } else {
            (this._subs[type] || (this._subs[type] = [])).push(func);
        }
    
        return this;
    };
    
    /**
     * 新增單次訂閱
     * 當該訂閱者在被通知執行一次之後會從訂閱器中移除
     * @param { string|Array<string> } type - 需要新增訂閱者的訂閱器名
     * @param { function } func - 訂閱者
     * @returns { object } - 釋出器例項,可實現鏈式呼叫
     */
    Publisher.prototype.once = function(type, func) {
        function onceAdd() {
            var args = Array.prototype.slice(arguments);
            this.destory(type, onceAdd);
            func.apply(this, args);
        }
        this.addSub(type, onceAdd);
    
        return this;
    };
    
    /**
     * 釋出通知
     * 通知指定訂閱器執行其中的每個訂閱者
     * @param { string } type - 需要通知其釋出訊息的訂閱器名
     * @returns { object } - 釋出器例項,可實現鏈式呼叫
     */
    Publisher.prototype.notify = function(type) {
        if(this._subs[type] === void 0) throw TypeError("Can't find the " + type + " event in the Publisher");
        
        var args = Array.prototype.slice.call(arguments, 1);
        this._subs[type].forEach(function(item) {
            item.apply(this, args);
        }, this);
    
        return this;
    };
    
    /**
     * 刪除訂閱器/訂閱者
     * 若沒有傳入引數直接呼叫方法,將清空整個訂閱器列表
     * 若只傳入了訂閱器型別引數,沒有指定刪除的訂閱者,則刪除該訂閱器
     * @param { string|Array<string> } type - 指定操作的訂閱器名,可用陣列傳入多個需要操作的訂閱器名
     * @param { function } func - 指定需要刪除的訂閱者
     * @return { object } - 釋出器例項,可實現鏈式呼叫
     */
    Publisher.prototype.destory = function(type, func) {
        if(func && typeof func !== 'function') throw TypeError('The param "func" should be a function!');
    
        if(!arguments.length) {
            this._subs = {};
            return this;
        }
        if(Array.isArray(type)) {
            type.forEach(function(item) {
                this.destory(item, func);
            }, this);
        }
        if(!func) {
            delete this._subs[type];
        } else {
            this._subs[type].forEach(function(item, index, array) {
                (item === func) && array.splice(index, 1);
            }, this);
        }
        return this;
    };
    
    return Publisher;
}());
複製程式碼

以上就是一個簡單的釋出訂閱器了,若有什麼不足的地方,還望各位同學指正!

相關文章