手寫一個node中的釋出訂閱

LeslieMay發表於2018-08-22

大家好,又見面了,我就是上篇文章說滾去寫業務程式碼,然後被產品折磨到如今才有點空閒時間就立馬爬來寫文章的熱愛學習和知識的程式設計師(不斷句,一氣呵成,自己甚至覺得有點小驕傲),好了,轉入正題,上次我們聊了聊關於事件環的知識,其中我們提到了一點是說node中的api大部分都是通過回撥函式和釋出訂閱的模式來進行的,ok,劃個重點,釋出訂閱!這就是我們今天文章的主題,也是整個node學習中較為重要的一部分,今天就帶大家來手寫一個node中的釋出訂閱~

釋出訂閱

之前我們聊過釋出訂閱的模式就是,當一個使用者訂閱了一些事件之後,如果這些事件觸發了,那我們就要去執行使用者在訂閱事件的時候定義的函式。

簡單的舉個例子,比如我們js中的addEventListenerjQuery中的on,這兩個相比我們都很熟悉,都是為某個dom元素進行繫結一個事件,比如我們為一個按鈕繫結了click事件,那麼當這個按鈕觸發了click事件之後,那麼我們繫結時候定義的函式也會被執行,

Events

大多數 Node.js 核心 API 都採用慣用的非同步事件驅動架構,其中某些型別的物件(觸發器)會週期性地觸發命名事件來呼叫函式物件(監聽器)。

node中有一個核心模組就是events,他實現的功能其實就是我們的釋出訂閱模式,上面的解釋其實就是說明了events這個模組在我們node的一些核心api中有些十分重要的作用,比如說我們的tcphttp,流這些都用到了我們的events模組。

我們就來看看events模組中的那些核心方法和屬性:

首先是第一塊,這一塊主要是說明了node中的一個對於新增事件或者移除事件時候要先觸發這對應的事件

  • newListener事件,node中定一個這樣一個規範,就是說當我們訂閱任何新事件的時候都會觸發一個newListener事件,傳入的引數就是訂閱的新事件名
  • removeListener事件,同上,當我們移除一個事件的事件就會觸發removeListener事件

這一部分是介紹事件的監聽器數量相關的函式

  • EventEmitter.defaultMaxListeners,規定了每個事件的監聽器(也就是回撥函式)的最大數量,預設是10,當超過這個數量的時候會報一個警告
  • emitter.setMaxListeners(n) 設定最大監聽數
  • emitter.getMaxListeners 返回 EventEmitter 當前的最大監聽器限制值

這一部分主要是對於註冊的事件或者某個已註冊的事件的監聽數陣列做的操作

  • emitter.eventNames 返回一個列出觸發器已註冊監聽器的事件的陣列
  • emitter.listenerCount(eventName) 返回正在監聽名為 eventName 的事件的監聽器的數量
  • emitter.listeners(eventName) 返回名為 eventName 的事件的監聽器陣列的副本

這部分是events的核心方法,on繫結監聽,once繫結的事件只觸發一次,emit用來發射事件

  • emitter.addListener 也就是on的別名,就是訂閱某一個事件比如 my.on('buy',()=>{console.log('沒錢了')})
  • emitter.on(eventName, listener) 新增 listener 函式到名為 eventName 的事件的監聽器陣列的末尾。 不會檢查 listener 是否已被新增。 多次呼叫並傳入相同的 eventNamelistener 會導致 listener 被新增與呼叫多次。
  • emitter.once(eventName, listener) 新增一個單次 listener 函式到名為 eventName 的事件。 下次觸發 eventName 事件時,監聽器會被移除,然後呼叫
  • emitter.prependListener(eventName, listener) 和on方法類似,但是呼叫這個方法會吧監聽器新增到陣列的首項
  • emitter.prependOnceListener(eventName, listener) 和once類似,也依然是講監聽器新增到陣列首項
  • emitter.emit 發射事件,會按照註冊事件時候放置的回撥函式的順序進行執行

這一部分是做事件移除

  • emitter.removeAllListeners([eventName]) 移除全部或指定 eventName 的監聽器
  • emitter.removeListener(eventName, listener)從名為 eventName 的事件的監聽器陣列中移除指定的 listener
  • emitter.off(eventName, listener) 就是removeListener的別名

My-EventEmitter

ok,老套路,既然我們都知道了這些方法是什麼意思 那麼我們就跟著來手寫一個EventEmitter!

function EventEmitter(){
    this._events = {} ;// 事件庫
    this.count = 0 ; // 同類事件最大的監聽數
}
// 最大監聽數預設是10
EventEmitter.defaultMaxListeners = 10;
// 獲取最大監聽數
EventEmitter.prototype.getMaxListeners = function(){
    return this.count || EventEmitter.defaultMaxListeners
}
// 設定最大監聽數
EventEmitter.prototype.setMaxListeners = function(n){
    this.count = n;
    return this
}
// addListener 和  on同樣的作用
EventEmitter.prototype.addListener = EventEmitter.prototype.on;

/**
 * 實現on方法
 * @param {string} eventName 訂閱事件名
 * @param {function} callback 回撥函式
 * @param {boolean} flag 是否新增到函式列表的第一位(是否第一次執行)
 */
EventEmitter.prototype.on = function(eventName,callback,flag){
    // 通常情況下 我們使用EventEmitter是通過util.inherits來繼承EventEmitter的公有方法,而EventEmitter的_events屬性是在例項上的 因此 如果檢測到是通過繼承拿到的 那麼就為這個類新增_events屬性
    if(!this._events) this._events = Object.create(null);
    // 如果事件名不是newListener 那麼檢測如果訂閱了newListener那麼就執行
    if(eventName !== 'newListener' && this._events["newListener"] && this._events["newListener"].length){
        this._events['newListener'].forEach(fn =>fn(eventName));
    }
    if(this._events[eventName]){
        // 如果之前新增過這個事件
        if(flag){
            this._events[eventName].unshift(callback)
        }else{
            this._events[eventName].push(callback)
        }
    }else{
        this._events[eventName] = [callback]
    }
     // 判斷訂閱事件數 超過最大個數 列印警告
     if (this._events[eventName].length >= this.getMaxListeners()) {
        console.warn('MaxListenersExceededWarning');
    }
}
/**
 * 返回當前訂閱的事件名集合
 */
EventEmitter.prototype.eventNames = function(){
    return Object.keys(this._events)
}
/**
 * 返回訂閱的事件繫結的函式個數
 * @param {string} eventName 
 */
EventEmitter.prototype.listenerCount = function(eventName){
    return (this._events[eventName] || []).length
}
/**
 * 返回訂閱的事件繫結函式的copy
 * @param {string} eventName 
 */
EventEmitter.prototype.listeners = function(eventName){
    return [...(this._events[eventName] || [])]
}

EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
/**
 * 移除某個事件下的執行函式
 * @param {string} eventName 
 * @param {function} callback 
 */
EventEmitter.prototype.removeListener = function(eventName,callback){
    if(this._events[eventName]){
        this._events[eventName] = this._events[eventName].filter((fn) => {
            return fn != callback && fn.realCallback !== callback;// 這裡判斷了fn是不是等於callback,同時也解決我們因為once操作而把原有的回撥函式包裹一層的問題,通過暴露出來的原有回撥作對比
        })
    }
    return this
}
/**
 * 移除某個事件的所有回撥
 * @param {string} eventName 
 */
EventEmitter.prototype.removeAllListeners = function(eventName){
    if(this._events[eventName]){
        this._events[eventName] = []
    }
    return this
}
/**
 * 新增一個單次 listener 函式到名為 eventName 的事件。 下次觸發 eventName 事件時,監聽器會被移除,然後呼叫。
 * @param {string} eventName 
 * @param {function} callback 
 * @param {boolean} flag 
 */
EventEmitter.prototype.once = function(eventName,callback,flag){
    function wrap(){
        callback();
        this.removeListener(eventName,wrap)
    }
    wrap.realCallback = callback;// 這裡因為once的回撥函式被重新包裹了一層 因此當你removeListener的時候如果用原來的fn去判斷是否相等的話就判斷不到了,因此需要把原來的fn暴露到一個屬性上,方便刪除操作的時候去做對比
    this.on(eventName,wrap,flag)
    return this;
}
/**
 * 向陣列前面新增事件
 * @param {string} eventName 
 * @param {function} callback 
 */
EventEmitter.prototype.prependListener = function (eventName, callback) {
    this.on(eventName, callback, true);
}
/**
 * 向陣列前面新增事件 並且只執行一次
 * @param {string} eventName 
 * @param {function} callback 
 */
EventEmitter.prototype.prependOnceListener = function(eventName, callback){
    this.once(eventName,callback,true);
}
/**
 * 觸發事件
 * @param {string} eventName 
 */
EventEmitter.prototype.emit = function (eventName) {
    if (this._events[eventName]) {
        this._events[eventName].forEach(fn => {
            fn.call(this);
        });
    }
}
複製程式碼

跟著思路來了一遍,是不是發現突然開竅,大喊一聲,學會了!快刪!

相關文章