釋出-訂閱模式在開發中的應用其實是很廣泛的,比如大家都知道的 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;
}());
複製程式碼
以上就是一個簡單的釋出訂閱器了,若有什麼不足的地方,還望各位同學指正!