發佈 — 訂閱模式又叫觀察者模式,它定義對象間的一種一對多的依賴關係,當一個對象的狀 態發生改變時,全部依賴於它的對象都將獲得通知。緩存
在 JavaScript開發中,咱們通常用事件模型來替代傳統的發佈 — 訂閱模式。bash
下面來模擬下EventEmitter的初步實現app
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是否爲真,來肯定是否移除這個回調。這樣能夠達到目的,可是在發起回調時,須要每一次都判斷,給通知方法增長了額外的負擔,來考慮一個更聰明的實現方式。函數
once(eventName, listener) {
function wrap(args) {
listener.apply(this, args);
this.removeListener(eventName, wrap);
}
wrap.cb = listener;//將回調存儲起來用於刪除時對比
this.on(eventName, wrap);
}
複製代碼
將回調函數包裹起來,在包裹函數內部移除原回調函數,而後將wrap函數添加進觀察者隊列。同時要將原回調函數存進wrap中,用在在移除原回調時判斷。ui
removeListener(eventName, listener) {
if(!this._events[eventName]) { return; }
this._events[eventName] = this._events[eventName].forEach(item => {
return item !== listener && item.cb !== listener;
});
}
複製代碼
比較有趣的是EventEmitter同時提供了newListener事件,每次添加觀察者(即便是第二次添加newListener)時都會觸發這個事件,在on方法中須要添加以下代碼:this
this.emmit('newListener', eventName, listener);//觸發newListener事件回調
複製代碼
這個靜態屬性限制了一種事件能夠添加的最大回調數量,同時還有配套的setMaxListeners和getMaxListeners方法來設置和獲取每一個事件能夠添加的最大回調數量spa
setMaxListeners(n) {
this.maxListeners = n;
}
getMaxListeners() {
return this.maxListeners ? this.maxListeners : EventEmitter.defaultMaxListeners;
}
複製代碼
on方法添加判斷;prototype
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);
});
}
}
複製代碼