發佈-訂閱模式在開發中的應用實際上是很普遍的,好比你們都知道的 Vue,使用數據驅動模板的改變,將咱們的雙手從繁瑣的 dom 操做中解放出來,稍微懂一些原理的同窗們都知道,其雙向數據綁定就是經過數據劫持、發佈-訂閱和 dom 模板編譯實現的,就算不了解這個的同窗,js 中的事件監聽相信作前端開發的同窗都寫過;前端
其實事件監聽就是一個訂閱的操做,當某個事件觸發的時候,對該事件監聽時傳入的 callback 就會被執行,這就是一個完整的發佈的-訂閱流程;接下來咱們就來簡單的寫一下一個發佈訂閱器:數組
俗話說:磨刀不誤砍柴工,在動手以前,縷清一下思路是有必要的,首先咱們要明確這個發佈訂閱器的功能,其中包括bash
基本功能:app
升級功能:dom
接下來咱們就分兩版上代碼:函數
/** * 發佈訂閱器構造函數 */ 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; }());
基礎版就如上面的代碼,以最簡單的方式實現一個發佈訂閱器的基本功能(能訂閱,並能接收消息,還能指定刪除)this
/** * 發佈訂閱器構造函數 */ 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; }());
以上就是一個簡單的發佈訂閱器了,如有什麼不足的地方,還望各位同窗指正!spa