大家好,又見面了,我就是上篇文章說滾去寫業務程式碼,然後被產品折磨到如今才有點空閒時間就立馬爬來寫文章的熱愛學習和知識的程式設計師(不斷句,一氣呵成,自己甚至覺得有點小驕傲),好了,轉入正題,上次我們聊了聊關於事件環的知識,其中我們提到了一點是說node
中的api
大部分都是通過回撥函式和釋出訂閱的模式來進行的,ok,劃個重點,釋出訂閱
!這就是我們今天文章的主題,也是整個node學習中較為重要的一部分,今天就帶大家來手寫一個node
中的釋出訂閱~
釋出訂閱
之前我們聊過釋出訂閱的模式就是,當一個使用者訂閱了一些事件之後,如果這些事件觸發了,那我們就要去執行使用者在訂閱事件的時候定義的函式。
簡單的舉個例子,比如我們js中的addEventListener
和jQuery
中的on
,這兩個相比我們都很熟悉,都是為某個dom元素進行繫結一個事件,比如我們為一個按鈕繫結了click
事件,那麼當這個按鈕觸發了click
事件之後,那麼我們繫結時候定義的函式也會被執行,
Events
大多數 Node.js 核心 API 都採用慣用的非同步事件驅動架構,其中某些型別的物件(觸發器)會週期性地觸發命名事件來呼叫函式物件(監聽器)。
node中有一個核心模組就是events
,他實現的功能其實就是我們的釋出訂閱模式,上面的解釋其實就是說明了events
這個模組在我們node
的一些核心api中有些十分重要的作用,比如說我們的tcp
,http
,流這些都用到了我們的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
是否已被新增。 多次呼叫並傳入相同的eventName
和listener
會導致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);
});
}
}
複製程式碼
跟著思路來了一遍,是不是發現突然開竅,大喊一聲,學會了!快刪!