釋出 — 訂閱模式
釋出 — 訂閱模式又叫觀察者模式,它定義物件間的一種一對多的依賴關係,當一個物件的狀 態發生改變時,所有依賴於它的物件都將得到通知。
在 JavaScript開發中,我們一般用事件模型來替代傳統的釋出 — 訂閱模式。
實現的關鍵要素
- 釋出者有一個訂閱者快取佇列
- 釋出者有增加和刪除訂閱者的方法
- 釋出者狀態改變,需要notify方法通知佇列中的所有訂閱者
- js中採用事件回撥的方式來更新訂閱者,因此訂閱者不再需要update方法
下面來模擬下EventEmitter的初步實現
class EventEmitter {
constructor() {
this._events = {};//用物件的方式來快取訂閱者佇列(事件名稱:回撥)
}
on(eventName, listener) {
if(typeof listener !== 'function') { return; }
if(!this._events) {//如果只被繼承了prototype,需要在繼承的物件上新增_events屬性
this._events = Object.create(null);
}
if(!this._events[eventName]) {//事件佇列不存在
this._events[eventName] = [];
}
this._events[eventName].push(listener);//新增觀察者
}
addListener(eventName, listener) {
this.on(eventName, listener);
}
removeListener(eventName, listener) {
if(!this._events[eventName]) { return; }
this._events[eventName] = this._events[eventName].forEach(item => {
return item !== listener;
});
}
emmit(eventName, ...args) {//狀態改變
if(!this._events[eventName]) { return; }
this._events[eventName].forEach(callback => {//通知所有的訂閱者,發起回撥
callback.apply(this, args);
});
}
}
複製程式碼
EventEmitter中的once方法可以做到繫結的事件只呼叫一次,之後不會再被呼叫,他的實現方式實在怎麼樣的?正常情況應該是在回撥函式被呼叫一次之後移除這個回撥。可以考慮在回撥函式上加上once屬性,在發起回撥的時候判斷once是否為真,來確定是否移除這個回撥。這樣可以達到目的,但是在發起回撥時,需要每一次都判斷,給通知方法增加了額外的負擔,來考慮一個更聰明的實現方式。
wrap函式
once(eventName, listener) {
function wrap(args) {
listener.apply(this, args);
this.removeListener(eventName, wrap);
}
wrap.cb = listener;//將回撥儲存起來用於刪除時對比
this.on(eventName, wrap);
}
複製程式碼
將回撥函式包裹起來,在包裹函式內部移除原回撥函式,然後將wrap函式新增進觀察者佇列。同時要將原回撥函式存進wrap中,用在在移除原回撥時判斷。
修改移除觀察者方法
removeListener(eventName, listener) {
if(!this._events[eventName]) { return; }
this._events[eventName] = this._events[eventName].forEach(item => {
return item !== listener && item.cb !== listener;
});
}
複製程式碼
newListener事件
比較有趣的是EventEmitter同時提供了newListener事件,每次新增觀察者(即使是第二次新增newListener)時都會觸發這個事件,在on方法中需要新增如下程式碼:
this.emmit('newListener', eventName, listener);//觸發newListener事件回撥
複製程式碼
defaultMaxListeners
這個靜態屬性限制了一種事件可以新增的最大回撥數量,同時還有配套的setMaxListeners和getMaxListeners方法來設定和獲取每個事件可以新增的最大回撥數量
setMaxListeners(n) {
this.maxListeners = n;
}
getMaxListeners() {
return this.maxListeners ? this.maxListeners : EventEmitter.defaultMaxListeners;
}
複製程式碼
on方法新增判斷;
if(this._events[eventName].length > this.getMaxListeners()){
console.warn('超過最大數量,請修改maxListeners')
}
複製程式碼
完整程式碼
class EventEmitter {
constructor() {
this._events = {};//用物件的方式來快取訂閱者佇列(事件名稱:回撥)
}
setMaxListeners(n) {
this.maxListeners = n;
}
getMaxListeners() {
return this.maxListeners ? this.maxListeners : EventEmitter.defaultMaxListeners;
}
on(eventName, listener) {
if(typeof listener !== 'function') { return; }
if(!this._events) {//如果只被繼承了prototype,需要在繼承的物件上新增_events屬性
this._events = Object.create(null);
}
this.emmit('newListener', eventName, listener);//觸發newListener事件回撥
if(!this._events[eventName]) {//事件佇列不存在
this._events[eventName] = [];
}
this._events[eventName].push(listener);//新增觀察者
if(this._events[eventName].length > this.getMaxListeners()){
console.warn('超過最大數量,請修改maxListeners')
}
}
once(eventName, listener) {
function wrap(args) {
listener.apply(this, args);
this.removeListener(eventName, wrap);
}
wrap.cb = listener;//將回撥儲存起來用於刪除時對比
this.on(eventName, wrap);
}
addListener(eventName, listener) {
this.on(eventName, listener);
}
removeListener(eventName, listener) {
if(!this._events[eventName]) { return; }
this._events[eventName] = this._events[eventName].forEach(item => {
return item !== listener && item.cb !== listener;
});
}
emmit(eventName, ...args) {//狀態改變
if(!this._events[eventName]) { return; }
this._events[eventName].forEach(callback => {//通知所有的訂閱者,發起回撥
callback.apply(this, args);
});
}
}
複製程式碼