小插曲
事件
- 建議大家看下官網中events事件的描述node中events事件
- 釋出訂閱模式定義了一種一對多的依賴關係
- 在Node中EventEmitter開放on(事件名,回撥函式)用於訂閱事件
- emit(事件名)用於釋出事件,可能對應多個訂閱事件,讓訂閱事件依次執行
不明白?沒關係。舉個最簡單例子,女人失戀了會哭,還會找新男朋友,在這裡哭和找男朋友相當於訂閱女人失戀的回撥,什麼時候執行呢?當釋出女人失戀這件事的時候,說的這麼抽象,直接來一段程式碼吧
- 是不是很簡單,只有釋出這個事件時候,被訂閱的事件才會依次執行,形成一對多的依賴關係。接下來直接寫原始碼實現
思路構建
- 我們想構造一個類似這樣的物件 {"失戀":[cry,findBoy]},當事件釋出的時候,讓陣列中對應的函式依次執行,就實現了這樣的效果
- 先講個小知識點 {}和Object.create(null)區別。 {}有作用鏈,通過Object.create(null)創造的空物件沒有作用鏈,給大家演示下,其它就沒啥區別。原始碼就是這樣寫(逼格高)
實現events模組
1、on和emit 兩個核心方法
- 原始碼實現
// 宣告EventEmitter事件發生器建構函式
function EventEmitter() {
this._events = Object.create(null);
}
//on 訂閱方法實現 因為在例項上呼叫,所以寫在原型上
EventEmitter.prototype.on = function(type,callback){
// 如果例項不存在則建立一個空物件,Object.create(null)沒有鏈
if(!this._events) {
this._events = Object.create(null);
}
if(this._events[type]){ //如果失戀有對應的值,直接往陣列push
this._events[type].push(callback)
}else { //第一次訂閱,沒有失戀,就宣告{失戀:[cry]}
this._events[type] = [callback];
}
};
// emit方法實現
EventEmitter.prototype.emit = function(type){
if(this._events[type]){ //{失戀:[cry,eat]} 如果失戀對應有值,依次執行裡面的方法
this._events[type].forEach(fn=>fn())
}
};
module.exports = EventEmitter
複製程式碼
- 十幾行程式碼就實現核心功能,這麼簡單?對 就是這麼簡單,趕快來測試下吧
2、removeListener 取消訂閱事件,失戀了不想哭了,所以我們提供個移除監聽的方法
- 比較簡單,直接上程式碼吧看的直接
// 移除訂閱事件的方法
EventEmitter.prototype.removeListener = function(type,callback){
if(this._events[type]){
// 返回false就表示不要了,用filter實現去重
this._events[type] = this._events[type].filter(fn=>fn!==callback)
}
};
複製程式碼
- 測試下吧,失戀了不想哭了
- 完美實現,是不是很激動。
3、removeAllListeners移除全部的監聽器,與removeListener相對應
// removeAllListeners 移除所有的監聽者
EventEmitter.prototype.removeAllListeners = function(){
//簡單粗暴,直接賦值空物件 {}
this._events = Object.create(null);
};
複製程式碼
- 測試下,失戀了既不想哭,也不想找物件,什麼也不列印就對拉
4、擴充套件once方法 我們希望哭的事件 多次釋出emit時候只執行一次,也就代表執行一次後需要將事件從對應關係中移除掉。
// once實現
EventEmitter.prototype.once = function(type,callback,flag){
// 先繫結 呼叫後再刪除,運用了one函式 {失戀:one}
let one = (...args)=> {
callback(...args);
this.removeListener(type, one);
}
//自定義屬性 因為例項中沒有one屬性
one.l = callback;
this.on(type,one)
};
// 移除訂閱事件的方法
EventEmitter.prototype.removeListener = function(type,callback){
if(this._events[type]){
// 返回false就表示不要了,用filter實現去重
this._events[type] = this._events[type].filter(fn=>fn!==callback && fn.l!==callback)
}
};
複製程式碼
- 你可能會疑惑為什麼宣告一個wrap函式,設想下,不然你告訴我怎麼先繫結一次,在移除。很多人可能都會這麼寫
- 錯誤例子 錯誤例子 錯誤例子(重要事情說三遍)
// - 錯誤例子 錯誤例子 錯誤例子(重要事情說三遍)
//你可能會這麼寫,但剛繫結就移除拉,體會這意思了吧
EventEmitter.prototype.once = function(type,callback){
//先繫結在移除
this.on(type,callback);
this.removeListener(type,callback)
};
複製程式碼
- 測試下吧,一步一個腳印
5、newListener方法。當cry新增到內部監聽陣列({失戀:[cry]})之前,會觸發自身的'newListener'事件
- 沒聽懂?我們先來看官方的用法
簡單說就是可以監控到訂閱的事件型別,上原始碼看下如何實現
//on 訂閱方法實現 因為在例項上呼叫,所以寫在原型上
EventEmitter.prototype.on = function(type,callback){
// 如果例項不存在則建立一個空物件,Object.create(null)沒有鏈
if(!this._events) {
this._events = Object.create(null);
}
if(type!=="newListener"){
if(this._events["newListener"]){
this._events["newListener"].forEach(fn=>fn(type))
}
}
if(this._events[type]){ //如果失戀有對應的值,直接往陣列push
this._events[type].push(callback)
}else { //第一次訂閱,沒有失戀,就宣告{失戀:[cry]}
this._events[type] = [callback];
}
};
複製程式碼
- 測試下吧