亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

循序漸進(jìn)教你實現(xiàn)一個完整的node的EventEmitter模塊

sunsmell / 2552人閱讀

摘要:本文從的的使用出發(fā),循序漸進(jìn)的實現(xiàn)一個完整的模塊。移除指定事件的某個監(jiān)聽器,監(jiān)聽器必須是該事件已經(jīng)注冊過的監(jiān)聽器的別名移除所有事件的所有監(jiān)聽器,如果指定事件,則移除指定事件的所有監(jiān)聽器。返回指定事件的監(jiān)聽器數(shù)組。

node的事件模塊只包含了一個類:EventEmitter。這個類在node的內(nèi)置模塊和第三方模塊中大量使用。EventEmitter本質(zhì)上是一個觀察者模式的實現(xiàn),這種模式可以擴展node在多個進(jìn)程或網(wǎng)絡(luò)中運行。本文從node的EventEmitter的使用出發(fā),循序漸進(jìn)的實現(xiàn)一個完整的EventEmitter模塊。

EventEmitter模塊的基本用法和簡單實現(xiàn)

node中常用的EventEmitter模塊的API

EventEmitter模塊的異常處理

完整的實現(xiàn)一個EventEmitter模塊

原文地址:https://github.com/forthealll...

如果文章對您有幫助,您的star是對我最好的鼓勵~

一、EventEmitter模塊的基本用法和簡單實現(xiàn) (1) EventEmitter模塊的基本用法

首先先了解一下EventEmitter模塊的基本用法,EventEmitter本質(zhì)上是一個觀察者模式的實現(xiàn),所謂觀察者模式:

它定義了對象間的一種一對多的關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個主題對象,當(dāng)一個對象發(fā)生改變時,所有依賴于它的對象都將得到通知。

因此最基本的EventEmitter功能,包含了一個觀察者和一個被監(jiān)聽的對象,對應(yīng)的實現(xiàn)就是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方法,發(fā)出say事件,通過eventEmitter的on方法監(jiān)聽,從而執(zhí)行相應(yīng)的函數(shù)。

(2) 簡單實現(xiàn)一個EventEmitter模塊

根據(jù)上述的例子,我們知道了EventEmitter模塊的基礎(chǔ)功能emit和on。下面我們實現(xiàn)一個包含emit和on方法的EventEmitter類。

on(eventName,callback)方法傳入兩個參數(shù),一個是事件名(eventName),另一個是相應(yīng)的回調(diào)函數(shù),我們選擇在on的時候針對事件名添加監(jiān)聽函數(shù),用對象來包含所有事件。在這個對象中對象名表示事件名(eventName),而對象的值是一個數(shù)組,表示該事件名所對應(yīng)的執(zhí)行函數(shù)。

emit(eventName,...arg)方法傳入的參數(shù),第一個為事件名,其他參數(shù)事件對應(yīng)的執(zhí)行函數(shù)中的實參,emit方法的功能就是從事件對象中,尋找對應(yīng)key為eventName的屬性,執(zhí)行該屬性所對應(yīng)的數(shù)組里面每一個執(zhí)行函數(shù)。

下面來實現(xiàn)一個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

上述就實現(xiàn)了一個簡單的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) 為指定事件添加一個監(jiān)聽器到監(jiān)聽器數(shù)組的尾部。
prependListener(event,listener) 與addListener相對,為指定事件添加一個監(jiān)聽器到監(jiān)聽器數(shù)組的頭部。
on(event, listener) 其實就是addListener的別名
once(event, listener) 為指定事件注冊一個單次監(jiān)聽器,即 監(jiān)聽器最多只會觸發(fā)一次,觸發(fā)后立刻解除該監(jiān)聽器。
removeListener(event, listener) 移除指定事件的某個監(jiān)聽器,監(jiān)聽器必須是該事件已經(jīng)注冊過的監(jiān)聽器
off(event, listener) removeListener的別名
removeAllListeners([event]) 移除所有事件的所有監(jiān)聽器, 如果指定事件,則移除指定事件的所有監(jiān)聽器。
setMaxListeners(n) 默認(rèn)情況下, EventEmitters 如果你添加的監(jiān)聽器超過 10 個就會輸出警告信息。 setMaxListeners 函數(shù)用于提高監(jiān)聽器的默認(rèn)限制的數(shù)量。
listeners(event) 返回指定事件的監(jiān)聽器數(shù)組。
emit(event, [arg1], [arg2], [...]) 按參數(shù)的順序執(zhí)行每個監(jiān)聽器,如果事件有注冊監(jiān)聽返回 true,否則返回 false。

除此之外,還有2個特殊的,不需要手動添加,node的EventEmitter模塊自帶的特殊事件:

事件名 事件描述
newListener 該事件在添加新事件監(jiān)聽器的時候觸發(fā)
removeListener 從指定監(jiān)聽器數(shù)組中刪除一個監(jiān)聽器。需要注意的是,此操作將會改變處于被刪監(jiān)聽器之后的那些監(jiān)聽器的索引

上述node的EventEmitter的模塊看起來很多很復(fù)雜,其實上述的API中包含了一些別名,仔細(xì)整理,理解其使用和實現(xiàn)不是很困難,下面一一對比和介紹上述的API。

(1) addListener和removeListener、on和off方法

addListener(eventName,listener)的作用是為指定事件添加一個監(jiān)聽器. 其別名為on

removeListener(eventName,listener)的作用是為移除某個事件的監(jiān)聽器. 其別名為off

再次需要強調(diào)的是: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");
//相應(yīng)的監(jiān)聽say事件的hello1事件被移除
//只輸出hello 2 Jony
(2) removeListener和removeAllListeners

removeListener指的是移除一個指定事件的某一個監(jiān)聽器,而removeAllListeners指的是移除某一個指定事件的全部監(jiān)聽器。
這里舉例一個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移除了所有關(guān)于say事件的監(jiān)聽
//因此沒有任何輸出
(3) on和once方法

on和once的區(qū)別是:

on的方法對于某一指定事件添加的監(jiān)聽器可以持續(xù)不斷的監(jiān)聽相應(yīng)的事件,而once方法添加的監(jiān)聽器,監(jiān)聽一次后,就會被消除。

比如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方法監(jiān)聽的事件,可以持續(xù)不斷的被觸發(fā)。

(4) 兩個特殊的事件newListener和removeListener

我們知道當(dāng)實例化EventEmitter模塊之后,監(jiān)聽對象是一個對象,包含了所有的監(jiān)聽事件,而這兩個特殊的方法就是針對監(jiān)聽事件的添加和移除的。

newListener:在添加新事件監(jiān)聽器觸發(fā)
removeListener:在移除事件監(jiān)聽器時觸發(fā)

以newListener為例,會在添加新事件監(jiān)聽器的時候觸發(fā):

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]

從上述的例子來看,每當(dāng)添加新的事件,都會自動的emit一個“newListener”事件,且參數(shù)為eventName(新事件的名)和listener(新事件的執(zhí)行函數(shù))。

同理特殊事件removeListener也是同樣的,當(dāng)事件被移除,會自動emit一個"removeListener"事件。

三、EventEmitter模塊的異常處理 (1) node中的try catch異常處理方法

在node中也可以通過try catch方式來捕獲和處理異常,比如:

try {
  let x=x;
} catch (e) {
  console.log(e);
}

上述let x=x 賦值語句的錯誤會被捕獲。這里提異常處理,那么跟事件有什么關(guān)系呢?

node中有一個特殊的事件error,如果異常沒有被捕獲,就會觸發(fā)process的uncaughtException事件拋出,如果你沒有注冊該事件的監(jiān)聽器(即該事件沒有被處理),則 Node.js 會在控制臺打印該異常的堆棧信息,并結(jié)束進(jìn)程。

比如:

var events=require("events");
var emitter=new events.EventEmitter();
emitter.emit("error");

在上述代碼中沒有監(jiān)聽error的事件函數(shù),因此會觸發(fā)process的uncaughtException事件,從而打印異常堆棧信息,并結(jié)束進(jìn)程。

對于阻塞或者說非異步的異常捕獲,try catch是沒有問題的,但是:

try catch不能捕獲非阻塞或者異步函數(shù)里面的異常。

舉例來說:

try {
  let x=x;//第二個x在使用前未定義,會拋出異常
} catch (e) {
  console.log("該異常已經(jīng)被捕獲");
  console.log(e);
}

上述代碼中,以為try方法里面是同步的,因此可以捕獲異常。如果try方法里面有異步的函數(shù):

try {
  process.nextTick(function(){
      let x=x; //第二個x在使用前未定義,會拋出異常
  });
} catch (e) {
  console.log("該異常已經(jīng)被捕獲");
  console.log(e);
}

因為process.nextTick是異步的,因此在process.nextTick內(nèi)部的錯誤不能被捕獲,也就是說try catch不能捕獲非阻塞函數(shù)內(nèi)的異常。

(2) 通過domains管理異常

node中domain模塊能被用來集中地處理多個異常操作,通過node的domain模塊可以捕獲非阻塞函數(shù)內(nèi)的異常。

var domain=require("domain");
var eventDomain=domain.create();
eventDomain.on("error",function(err){
  console.log("該異常已經(jīng)被捕獲了");
  console.log(err);
});
eventDomain.run(function(){
   process.nextTick(function(){
     let x=x;//拋出異常
   });
});

同樣的,即使process.nextTick是一個異步函數(shù),domain.on方法也可以捕獲這個異步函數(shù)中的異常。

即使更復(fù)雜的情況下,比如異步嵌套異步的情況下,domain.on方法也可以捕獲異常。

var domain=require("domain");
var eventDomain=domain.create();
eventDomain.on("error",function(err){
  console.log("該異常已經(jīng)被捕獲了");
  console.log(err);
});
eventDomain.run(function(){
   process.nextTick(function(){
     setTimeout(function(){
       setTimeout(function(){
         let x=x;
       },0);
     },0);
   });
});

在上述的情況下,即使異步嵌套很復(fù)雜,也能在最外層捕獲到異常。

(3) domain模塊缺陷

在node最新的文檔中,domain被廢除(被標(biāo)記為: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的,原因在于發(fā)出異常的EventEmitter實例e,以及觸發(fā)異常的定時函數(shù)timer沒有被domain包裹。domain模塊是通過重寫事件循環(huán)中的nextTick和_tickCallback來事件將process.domain注入到next包裹的所有異步事件內(nèi)。

解決上述無法捕獲異常的情況,只需要將e或者timer包裹進(jìn)domain。

d.add(e)或者d.add(timer)

就可以成功的捕獲異常。

domain模塊已經(jīng)在node最新的文檔中被廢除

(4)process.on("uncaughtException")的方法捕獲異常

node中提供了一個最外層的兜底的捕獲異常的方法。非阻塞或者異步函數(shù)中的異常都會拋出到最外層,如果異常沒有被捕獲,那么會暴露出來,被最外層的process.on("uncaughtException")所捕獲。

try {
  process.nextTick(function(){
     let x=x; //第二個x在使用前未定義,會拋出異常
  },0);
} catch (e) {
  console.log("該異常已經(jīng)被捕獲");
  console.log(e);
}
process.on("uncaughtException",function(err){console.log(err)})

這樣就能在最外層捕獲異步或者說非阻塞函數(shù)中的異常。

四、完整的實現(xiàn)一個EventEmitter模塊(可選讀)

在第二節(jié)中我們知道了EventEmitter模塊的基本用法,那么根據(jù)基本的API我們可以進(jìn)一步自己去實現(xiàn)一個EventEmitter模塊。
每一個EventEmitter實例都有一個包含所有事件的對象_events,
事件的監(jiān)聽和監(jiān)聽事件的觸發(fā),以及監(jiān)聽事件的移除等都在這個對象_events的基礎(chǔ)上實現(xiàn)。

(1) emit

emit的方法實現(xiàn)的大致功能如下程序流程圖所示:

從上述的程序圖出發(fā),我們開始實現(xiàn)自己的EventEmitter模塊。

首先生成一個EventEmitter類,在類的初始化方法中生成這個事件對象_events.

class EventEmitter{
  constructor(){
    if(this._events===undefined){
      this._events=Object.create(null);//定義事件對象
      this._eventsCount=0;
    }
  }
}

_eventsCount用于統(tǒng)計事件的個數(shù),也就是_events對象有多少個屬性。

接著我們來實現(xiàn)emit方法,根據(jù)框圖,我們知道emit所做的事情是在_events對象中取出相應(yīng)type的屬性,并執(zhí)行屬性所對應(yīng)的函數(shù),我們來實現(xiàn)這個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];
    //判斷相應(yīng)type的執(zhí)行函數(shù)是否為一個函數(shù)還是一個數(shù)組
    if(typeof handler==="function"){
      Reflect.apply(handler,this,args);
    }else{
      const len=handler.length;
      for(var i=0;li
(2) on、addListener和prependListener方法

emit方法是出發(fā)事件,并執(zhí)行相應(yīng)的方法,而on方法則是對于指定的事件添加監(jiān)聽函數(shù)。用程序來說,就是往事件對象中_events添加相應(yīng)的屬性.程序流程圖如下所示:

接著我們來實現(xiàn)這個方法:

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];
    //判斷相應(yīng)的type的方法是否存在
    if(existing===undefined){
      //如果相應(yīng)的type的方法不存在,這新增一個相應(yīng)type的事件
      existing=events[type]=listener;
      ++this._eventsCount;
    }else{
      //如果存在相應(yīng)的type的方法,判斷相應(yīng)的type的方法是一個數(shù)組還是僅僅只是一個方法
      //如果僅僅是
      if(typeof existing==="function"){
        //如果僅僅是一個方法,則添加
        existing=events[type]=prepend?[listener,existing]:[existing,listener];
      }else if(prepend){
        existing.unshift(listener);
      }else{
        existing.push(listener);
      }
    }
    //鏈?zhǔn)秸{(diào)用
    return this;
}

在on方法中為了可以鏈?zhǔn)降恼{(diào)用我們返回了EventEmitter模塊的實例化本身。

且在on方法的參數(shù)中,第三個參數(shù)用于指定是在相應(yīng)事件類型屬性所對應(yīng)的數(shù)組頭部添加還是尾部添加,不傳的情況下實在尾部添加,如果指定prepend為true,則相同事件類型的新的監(jiān)聽事件會添加到事件數(shù)組的頭部。

如果_events存在newListener屬性,也就是說_event存在監(jiān)聽newListener監(jiān)聽事件,那么每次on方法添加事件的時候,都會emit出一個‘newListener’方法。

在on方法的基礎(chǔ)上可以實現(xiàn)addListener方法和prependListener方法。

addListener方法是on方法的別名:

EventEmitter.prototype.addListener=EventEmitter.prototype.on

prependListener方法相當(dāng)于在頭部添加,指定prepend為true:

EventEmitter.prototype.prependListener =
function prependListener(type, listener) {
  return EventEmitter.prototype.on(type, listener, true);
};
(3) removeListener和removeAllListeners

接著來看移除事件監(jiān)聽的方法removeListener和removeAllListeners,下面我們來看removeListener的程序流程圖:

接著來看removeListener的代碼:

removeListener(type,listener){
 var list,events,position,i,originalListener;
 events=this._events;
 list=events[type];
 //如果相應(yīng)的事件對象的屬性值是一個函數(shù),也就是說事件只被一個函數(shù)監(jiān)聽
 if(list===listener){
    if(--this._eventsCount===0){
      this._events=Object.create(null);
    }else{
      delete events[type];
      //如果存在對移除事件removeListener的監(jiān)聽函數(shù),則觸發(fā)removeListener
      if(events.removeListener)
         this.emit("removeListener",type,listener);
    }
 }else if(typeof list!=="function"){
   //如果相應(yīng)的事件對象屬性值是一個函數(shù)數(shù)組
   //遍歷這個數(shù)組,找出listener對應(yīng)的那個函數(shù),在數(shù)組中的位置
   for(i=list.length-1;i>=0;i--){
     if(list[i]===listener){
       position=i;
       break;
     }
   }
   //沒有找到這個函數(shù),則返回不做任何改動的對象
   if(position){
     return this;
   }
   //如果數(shù)組的第一個函數(shù)才是所需要刪除的對應(yīng)listener函數(shù),則直接移除
   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;
}

如果在之間設(shè)置了對于移除這個特殊事件“removeListener”的監(jiān)聽,那么就會在移除事件時候觸發(fā)“removeListener”事件。

最后來看removeAllListener,這個與removeListener相似,只要找到傳入的type所對應(yīng)屬性的值,沒有遍歷過程,直接刪除這個屬性即可。

除此之外,還有其他的類似與once、setMaxListeners、listeners也可以在此基礎(chǔ)上實現(xiàn),就不一一舉例。

五、總結(jié)

本文從node的EventEmitter模塊的使用出發(fā),介紹了EventEmitter提供的常用API,然后介紹了node中基于EventEmitter的異常處理,最后自己實現(xiàn)了一個較為完整的EventEmitter模塊。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/108099.html

相關(guān)文章

  • 【全文】狼叔:如何正確學(xué)習(xí)Node.js

    摘要:感謝大神的免費的計算機編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡介現(xiàn)在,越來越多的科技公司和開發(fā)者開始使用開發(fā)各種應(yīng)用。 說明 2017-12-14 我發(fā)了一篇文章《沒用過Node.js,就別瞎逼逼》是因為有人在知乎上黑Node.js。那篇文章的反響還是相當(dāng)不錯的,甚至連著名的hax賀老都很認(rèn)同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...

    Edison 評論0 收藏0
  • 【全文】狼叔:如何正確學(xué)習(xí)Node.js

    摘要:感謝大神的免費的計算機編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡介現(xiàn)在,越來越多的科技公司和開發(fā)者開始使用開發(fā)各種應(yīng)用。 說明 2017-12-14 我發(fā)了一篇文章《沒用過Node.js,就別瞎逼逼》是因為有人在知乎上黑Node.js。那篇文章的反響還是相當(dāng)不錯的,甚至連著名的hax賀老都很認(rèn)同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...

    fengxiuping 評論0 收藏0
  • Node.js設(shè)計模式》Node.js基本模式

    摘要:回調(diào)函數(shù)是在異步操作完成后傳播其操作結(jié)果的函數(shù),總是用來替代同步操作的返回指令。下面的圖片顯示了中事件循環(huán)過程當(dāng)異步操作完成時,執(zhí)行權(quán)就會交給這個異步操作開始的地方,即回調(diào)函數(shù)。 本系列文章為《Node.js Design Patterns Second Edition》的原文翻譯和讀書筆記,在GitHub連載更新,同步翻譯版鏈接。 歡迎關(guān)注我的專欄,之后的博文將在專欄同步: Enc...

    Seay 評論0 收藏0
  • KOA2框架原理解析和實現(xiàn)

    摘要:實現(xiàn)的四大模塊上文簡述了源碼的大體框架結(jié)構(gòu),接下來我們來實現(xiàn)一個的框架,筆者認(rèn)為理解和實現(xiàn)一個框架需要實現(xiàn)四個大模塊,分別是封裝創(chuàng)建類構(gòu)造函數(shù)構(gòu)造對象中間件機制和剝洋蔥模型的實現(xiàn)錯誤捕獲和錯誤處理下面我們就逐一分析和實現(xiàn)。 什么是koa框架? ? ? ? ?koa是一個基于node實現(xiàn)的一個新的web框架,它是由express框架的原班人馬打造的。它的特點是優(yōu)雅、簡潔、表達(dá)力強、自由度...

    tracymac7 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<