摘要:本文從的的使用出發,循序漸進的實現一個完整的模塊。移除指定事件的某個監聽器,監聽器必須是該事件已經注冊過的監聽器的別名移除所有事件的所有監聽器,如果指定事件,則移除指定事件的所有監聽器。返回指定事件的監聽器數組。
node的事件模塊只包含了一個類:EventEmitter。這個類在node的內置模塊和第三方模塊中大量使用。EventEmitter本質上是一個觀察者模式的實現,這種模式可以擴展node在多個進程或網絡中運行。本文從node的EventEmitter的使用出發,循序漸進的實現一個完整的EventEmitter模塊。
EventEmitter模塊的基本用法和簡單實現
node中常用的EventEmitter模塊的API
EventEmitter模塊的異常處理
完整的實現一個EventEmitter模塊
原文地址:https://github.com/forthealll...
如果文章對您有幫助,您的star是對我最好的鼓勵~
一、EventEmitter模塊的基本用法和簡單實現 (1) EventEmitter模塊的基本用法首先先了解一下EventEmitter模塊的基本用法,EventEmitter本質上是一個觀察者模式的實現,所謂觀察者模式:
它定義了對象間的一種一對多的關系,讓多個觀察者對象同時監聽某一個主題對象,當一個對象發生改變時,所有依賴于它的對象都將得到通知。
因此最基本的EventEmitter功能,包含了一個觀察者和一個被監聽的對象,對應的實現就是EventEmitter中的on和emit:
var events=require("events"); var eventEmitter=new events.EventEmitter(); eventEmitter.on("say",function(name){ console.log("Hello",name); }) eventEmitter.emit("say","Jony yu");
eventEmitter是EventEmitter模塊的一個實例,eventEmitter的emit方法,發出say事件,通過eventEmitter的on方法監聽,從而執行相應的函數。
(2) 簡單實現一個EventEmitter模塊根據上述的例子,我們知道了EventEmitter模塊的基礎功能emit和on。下面我們實現一個包含emit和on方法的EventEmitter類。
on(eventName,callback)方法傳入兩個參數,一個是事件名(eventName),另一個是相應的回調函數,我們選擇在on的時候針對事件名添加監聽函數,用對象來包含所有事件。在這個對象中對象名表示事件名(eventName),而對象的值是一個數組,表示該事件名所對應的執行函數。
emit(eventName,...arg)方法傳入的參數,第一個為事件名,其他參數事件對應的執行函數中的實參,emit方法的功能就是從事件對象中,尋找對應key為eventName的屬性,執行該屬性所對應的數組里面每一個執行函數。
下面來實現一個EventEmitter類
class EventEmitter{ constructor(){ this.handler={}; } on(eventName,callback){ if(!this.handles){ this.handles={}; } if(!this.handles[eventName]){ this.handles[eventName]=[]; } this.handles[eventName].push(callback); } emit(eventName,...arg){ if(this.handles[eventName]){ for(var i=0;i上述就實現了一個簡單的EventEmitter類,下面來實例化:
let event=new EventEmitter(); event.on("say",function(str){ console.log(str); }); event.emit("say","hello Jony yu"); //輸出hello Jony yu二、node中常用的EventEmitter模塊的API跟在上述簡單的EventEmitter模塊不同,node的EventEmitter還包含了很多常用的API,我們一一來介紹幾個實用的API.
方法名 方法描述 addListener(event, listener) 為指定事件添加一個監聽器到監聽器數組的尾部。 prependListener(event,listener) 與addListener相對,為指定事件添加一個監聽器到監聽器數組的頭部。 on(event, listener) 其實就是addListener的別名 once(event, listener) 為指定事件注冊一個單次監聽器,即 監聽器最多只會觸發一次,觸發后立刻解除該監聽器。 removeListener(event, listener) 移除指定事件的某個監聽器,監聽器必須是該事件已經注冊過的監聽器 off(event, listener) removeListener的別名 removeAllListeners([event]) 移除所有事件的所有監聽器, 如果指定事件,則移除指定事件的所有監聽器。 setMaxListeners(n) 默認情況下, EventEmitters 如果你添加的監聽器超過 10 個就會輸出警告信息。 setMaxListeners 函數用于提高監聽器的默認限制的數量。 listeners(event) 返回指定事件的監聽器數組。 emit(event, [arg1], [arg2], [...]) 按參數的順序執行每個監聽器,如果事件有注冊監聽返回 true,否則返回 false。 除此之外,還有2個特殊的,不需要手動添加,node的EventEmitter模塊自帶的特殊事件:
事件名 事件描述 newListener 該事件在添加新事件監聽器的時候觸發 removeListener 從指定監聽器數組中刪除一個監聽器。需要注意的是,此操作將會改變處于被刪監聽器之后的那些監聽器的索引 上述node的EventEmitter的模塊看起來很多很復雜,其實上述的API中包含了一些別名,仔細整理,理解其使用和實現不是很困難,下面一一對比和介紹上述的API。
(1) addListener和removeListener、on和off方法addListener(eventName,listener)的作用是為指定事件添加一個監聽器. 其別名為on
removeListener(eventName,listener)的作用是為移除某個事件的監聽器. 其別名為off
再次需要強調的是:addListener的別名是on,removeListener的別名是off
EventEmitter.prototype.on=EventEmitter.prototype.addListener EventEmitter.prototype.off=EventEmitter.prototype.removeListener接著我們來看具體的用法:
var events=require("events"); var emitter=new events.EventEmitter(); function hello1(name){ console.log("hello 1",name); } function hello2(name){ console.log("hello 2",name); } emitter.addListener("say",hello1); emitter.addListener("say",hello2); emitter.emit("say","Jony"); //輸出hello 1 Jony //輸出hello 2 Jony emitter.removeListener("say",hello1); emitter.emit("say","Jony"); //相應的監聽say事件的hello1事件被移除 //只輸出hello 2 Jony(2) removeListener和removeAllListenersremoveListener指的是移除一個指定事件的某一個監聽器,而removeAllListeners指的是移除某一個指定事件的全部監聽器。
這里舉例一個removeAllListeners的例子:var events=require("events"); var emitter=new events.EventEmitter(); function hello1(name){ console.log("hello 1",name); } function hello2(name){ console.log("hello 2",name); } emitter.addListener("say",hello1); emitter.addListener("say",hello2); emitter.removeAllListeners("say"); emitter.emit("say","Jony"); //removeAllListeners移除了所有關于say事件的監聽 //因此沒有任何輸出(3) on和once方法on和once的區別是:
on的方法對于某一指定事件添加的監聽器可以持續不斷的監聽相應的事件,而once方法添加的監聽器,監聽一次后,就會被消除。
比如on方法(跟addListener相同):
var events=require("events"); var emitter=new events.EventEmitter(); function hello(name){ console.log("hello",name); } emitter.on("say",hello); emitter.emit("say","Jony"); emitter.emit("say","yu"); emitter.emit("say","me"); //會一次輸出 hello Jony、hello yu、hello me也就是說on方法監聽的事件,可以持續不斷的被觸發。
(4) 兩個特殊的事件newListener和removeListener我們知道當實例化EventEmitter模塊之后,監聽對象是一個對象,包含了所有的監聽事件,而這兩個特殊的方法就是針對監聽事件的添加和移除的。
newListener:在添加新事件監聽器觸發
removeListener:在移除事件監聽器時觸發以newListener為例,會在添加新事件監聽器的時候觸發:
var events=require("events"); var emitter=new events.EventEmitter(); function hello(name){ console.log("hello",name); } emitter.on("newListener",function(eventName,listener){ console.log(eventName); console.log(listener); }); emitter.addListener("say",hello); //輸出say和[Function: hello]從上述的例子來看,每當添加新的事件,都會自動的emit一個“newListener”事件,且參數為eventName(新事件的名)和listener(新事件的執行函數)。
同理特殊事件removeListener也是同樣的,當事件被移除,會自動emit一個"removeListener"事件。
三、EventEmitter模塊的異常處理 (1) node中的try catch異常處理方法在node中也可以通過try catch方式來捕獲和處理異常,比如:
try { let x=x; } catch (e) { console.log(e); }上述let x=x 賦值語句的錯誤會被捕獲。這里提異常處理,那么跟事件有什么關系呢?
node中有一個特殊的事件error,如果異常沒有被捕獲,就會觸發process的uncaughtException事件拋出,如果你沒有注冊該事件的監聽器(即該事件沒有被處理),則 Node.js 會在控制臺打印該異常的堆棧信息,并結束進程。
比如:
var events=require("events"); var emitter=new events.EventEmitter(); emitter.emit("error");在上述代碼中沒有監聽error的事件函數,因此會觸發process的uncaughtException事件,從而打印異常堆棧信息,并結束進程。
對于阻塞或者說非異步的異常捕獲,try catch是沒有問題的,但是:
try catch不能捕獲非阻塞或者異步函數里面的異常。
舉例來說:
try { let x=x;//第二個x在使用前未定義,會拋出異常 } catch (e) { console.log("該異常已經被捕獲"); console.log(e); }上述代碼中,以為try方法里面是同步的,因此可以捕獲異常。如果try方法里面有異步的函數:
try { process.nextTick(function(){ let x=x; //第二個x在使用前未定義,會拋出異常 }); } catch (e) { console.log("該異常已經被捕獲"); console.log(e); }因為process.nextTick是異步的,因此在process.nextTick內部的錯誤不能被捕獲,也就是說try catch不能捕獲非阻塞函數內的異常。
(2) 通過domains管理異常node中domain模塊能被用來集中地處理多個異常操作,通過node的domain模塊可以捕獲非阻塞函數內的異常。
var domain=require("domain"); var eventDomain=domain.create(); eventDomain.on("error",function(err){ console.log("該異常已經被捕獲了"); console.log(err); }); eventDomain.run(function(){ process.nextTick(function(){ let x=x;//拋出異常 }); });同樣的,即使process.nextTick是一個異步函數,domain.on方法也可以捕獲這個異步函數中的異常。
即使更復雜的情況下,比如異步嵌套異步的情況下,domain.on方法也可以捕獲異常。
var domain=require("domain"); var eventDomain=domain.create(); eventDomain.on("error",function(err){ console.log("該異常已經被捕獲了"); console.log(err); }); eventDomain.run(function(){ process.nextTick(function(){ setTimeout(function(){ setTimeout(function(){ let x=x; },0); },0); }); });在上述的情況下,即使異步嵌套很復雜,也能在最外層捕獲到異常。
(3) domain模塊缺陷在node最新的文檔中,domain被廢除(被標記為:Deprecated),domain從誕生之日起就有著缺陷,舉例來說:
var domain = require("domain"); var EventEmitter = require("events").EventEmitter; var e = new EventEmitter(); var timer = setTimeout(function () { e.emit("data"); }, 10); function next() { e.once("data", function () { throw new Error("something wrong here"); }); } var d = domain.create(); d.on("error", function () { console.log("cache by domain"); }); d.run(next);如上述的代碼是無法捕獲到異常Error的,原因在于發出異常的EventEmitter實例e,以及觸發異常的定時函數timer沒有被domain包裹。domain模塊是通過重寫事件循環中的nextTick和_tickCallback來事件將process.domain注入到next包裹的所有異步事件內。
解決上述無法捕獲異常的情況,只需要將e或者timer包裹進domain。
d.add(e)或者d.add(timer)就可以成功的捕獲異常。
domain模塊已經在node最新的文檔中被廢除
(4)process.on("uncaughtException")的方法捕獲異常node中提供了一個最外層的兜底的捕獲異常的方法。非阻塞或者異步函數中的異常都會拋出到最外層,如果異常沒有被捕獲,那么會暴露出來,被最外層的process.on("uncaughtException")所捕獲。
try { process.nextTick(function(){ let x=x; //第二個x在使用前未定義,會拋出異常 },0); } catch (e) { console.log("該異常已經被捕獲"); console.log(e); } process.on("uncaughtException",function(err){console.log(err)})這樣就能在最外層捕獲異步或者說非阻塞函數中的異常。
四、完整的實現一個EventEmitter模塊(可選讀)在第二節中我們知道了EventEmitter模塊的基本用法,那么根據基本的API我們可以進一步自己去實現一個EventEmitter模塊。
(1) emit
每一個EventEmitter實例都有一個包含所有事件的對象_events,
事件的監聽和監聽事件的觸發,以及監聽事件的移除等都在這個對象_events的基礎上實現。emit的方法實現的大致功能如下程序流程圖所示:
從上述的程序圖出發,我們開始實現自己的EventEmitter模塊。
首先生成一個EventEmitter類,在類的初始化方法中生成這個事件對象_events.
class EventEmitter{ constructor(){ if(this._events===undefined){ this._events=Object.create(null);//定義事件對象 this._eventsCount=0; } } }_eventsCount用于統計事件的個數,也就是_events對象有多少個屬性。
接著我們來實現emit方法,根據框圖,我們知道emit所做的事情是在_events對象中取出相應type的屬性,并執行屬性所對應的函數,我們來實現這個emit方法。
class EventEmitter{ constructor(){ if(this._events===undefined){ this._events=Object.create(null);//定義事件對象 this._eventsCount=0; } } emit(type,...args){ const events=this._events; const handler=events[type]; //判斷相應type的執行函數是否為一個函數還是一個數組 if(typeof handler==="function"){ Reflect.apply(handler,this,args); }else{ const len=handler.length; for(var i=0;li(2) on、addListener和prependListener方法 emit方法是出發事件,并執行相應的方法,而on方法則是對于指定的事件添加監聽函數。用程序來說,就是往事件對象中_events添加相應的屬性.程序流程圖如下所示:
接著我們來實現這個方法:
on(type,listener,prepend){ var m; var events; var existing; events=this._events; //添加事件的 if(events.newListener!==undefined){ this.emit("newListener",type,listener); events=target._events; } existing=events[type]; //判斷相應的type的方法是否存在 if(existing===undefined){ //如果相應的type的方法不存在,這新增一個相應type的事件 existing=events[type]=listener; ++this._eventsCount; }else{ //如果存在相應的type的方法,判斷相應的type的方法是一個數組還是僅僅只是一個方法 //如果僅僅是 if(typeof existing==="function"){ //如果僅僅是一個方法,則添加 existing=events[type]=prepend?[listener,existing]:[existing,listener]; }else if(prepend){ existing.unshift(listener); }else{ existing.push(listener); } } //鏈式調用 return this; }在on方法中為了可以鏈式的調用我們返回了EventEmitter模塊的實例化本身。
且在on方法的參數中,第三個參數用于指定是在相應事件類型屬性所對應的數組頭部添加還是尾部添加,不傳的情況下實在尾部添加,如果指定prepend為true,則相同事件類型的新的監聽事件會添加到事件數組的頭部。
如果_events存在newListener屬性,也就是說_event存在監聽newListener監聽事件,那么每次on方法添加事件的時候,都會emit出一個‘newListener’方法。
在on方法的基礎上可以實現addListener方法和prependListener方法。
addListener方法是on方法的別名:
EventEmitter.prototype.addListener=EventEmitter.prototype.onprependListener方法相當于在頭部添加,指定prepend為true:
EventEmitter.prototype.prependListener = function prependListener(type, listener) { return EventEmitter.prototype.on(type, listener, true); };(3) removeListener和removeAllListeners接著來看移除事件監聽的方法removeListener和removeAllListeners,下面我們來看removeListener的程序流程圖:
接著來看removeListener的代碼:
removeListener(type,listener){ var list,events,position,i,originalListener; events=this._events; list=events[type]; //如果相應的事件對象的屬性值是一個函數,也就是說事件只被一個函數監聽 if(list===listener){ if(--this._eventsCount===0){ this._events=Object.create(null); }else{ delete events[type]; //如果存在對移除事件removeListener的監聽函數,則觸發removeListener if(events.removeListener) this.emit("removeListener",type,listener); } }else if(typeof list!=="function"){ //如果相應的事件對象屬性值是一個函數數組 //遍歷這個數組,找出listener對應的那個函數,在數組中的位置 for(i=list.length-1;i>=0;i--){ if(list[i]===listener){ position=i; break; } } //沒有找到這個函數,則返回不做任何改動的對象 if(position){ return this; } //如果數組的第一個函數才是所需要刪除的對應listener函數,則直接移除 if(position===0){ list.shift(); }else{ list.splice(position,1); } if(list.length===1) events[type]=list[0]; if(events.removeListener!==undefined) this.emit("removeListener",type,listener); } return this; }如果在之間設置了對于移除這個特殊事件“removeListener”的監聽,那么就會在移除事件時候觸發“removeListener”事件。
最后來看removeAllListener,這個與removeListener相似,只要找到傳入的type所對應屬性的值,沒有遍歷過程,直接刪除這個屬性即可。
除此之外,還有其他的類似與once、setMaxListeners、listeners也可以在此基礎上實現,就不一一舉例。
五、總結本文從node的EventEmitter模塊的使用出發,介紹了EventEmitter提供的常用API,然后介紹了node中基于EventEmitter的異常處理,最后自己實現了一個較為完整的EventEmitter模塊。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108099.html
摘要:感謝大神的免費的計算機編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學習簡介現在,越來越多的科技公司和開發者開始使用開發各種應用。 說明 2017-12-14 我發了一篇文章《沒用過Node.js,就別瞎逼逼》是因為有人在知乎上黑Node.js。那篇文章的反響還是相當不錯的,甚至連著名的hax賀老都很認同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...
摘要:感謝大神的免費的計算機編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學習簡介現在,越來越多的科技公司和開發者開始使用開發各種應用。 說明 2017-12-14 我發了一篇文章《沒用過Node.js,就別瞎逼逼》是因為有人在知乎上黑Node.js。那篇文章的反響還是相當不錯的,甚至連著名的hax賀老都很認同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...
摘要:回調函數是在異步操作完成后傳播其操作結果的函數,總是用來替代同步操作的返回指令。下面的圖片顯示了中事件循環過程當異步操作完成時,執行權就會交給這個異步操作開始的地方,即回調函數。 本系列文章為《Node.js Design Patterns Second Edition》的原文翻譯和讀書筆記,在GitHub連載更新,同步翻譯版鏈接。 歡迎關注我的專欄,之后的博文將在專欄同步: Enc...
摘要:實現的四大模塊上文簡述了源碼的大體框架結構,接下來我們來實現一個的框架,筆者認為理解和實現一個框架需要實現四個大模塊,分別是封裝創建類構造函數構造對象中間件機制和剝洋蔥模型的實現錯誤捕獲和錯誤處理下面我們就逐一分析和實現。 什么是koa框架? ? ? ? ?koa是一個基于node實現的一個新的web框架,它是由express框架的原班人馬打造的。它的特點是優雅、簡潔、表達力強、自由度...
閱讀 3189·2021-11-10 11:35
閱讀 1295·2019-08-30 13:20
閱讀 1117·2019-08-29 16:18
閱讀 2130·2019-08-26 13:54
閱讀 2154·2019-08-26 13:50
閱讀 955·2019-08-26 13:39
閱讀 2473·2019-08-26 12:08
閱讀 1951·2019-08-26 10:37