Node.js 教程 05 - EventEmitter(事件監聽/發射器 )


 

目錄:

前言

Node.js事件驅動介紹

Node.js事件

注冊並發射自定義Node.js事件

EventEmitter介紹

EventEmitter常用的API

error事件

繼承EventEmitter

 


 

 

前言:

  今天事兒太多了,沒有發太多的東西。不好意思了各位。

 

  本篇主要介紹Node.js中的事件驅動,至於Node.js事件概念的東西,太多了。

  本系列課程主要抱着的理念就是,讓大家慢慢的入門,我也盡量寫的簡單一點。

  所以呢,本文事件驅動,大家的目標應該是:理解Node.js的事件驅動、會使用注冊事件及發射事件即可。

  其他的只作為了解,在這里就一筆而過了,如果大家想深入了解,請自行百度。

 


 

 

Node.js事件驅動介紹:

  Node.js在Github上有一句短短的介紹:Evented I/O for V8 Javascript。

  一句話,霸氣側漏:基於V8引擎實現的事件驅動I/O,因此,Node.js也以事件驅動著名、通過異步的編程達到高吞吐量高性能。

  

  Node.js能在眾多的后端Javascript技術中脫穎而出,正是因其事件的特點而受到歡迎。

  拿Rhino來做比較,可以看出Rhino引擎支持的后端JavaScript擺脫不掉其他語言同步執行的影響,導致JavaScript在后端編程與前端編程之間有着十分顯著的差別,在編程模型上無法形成統一。

  在前端編程中,事件的應用十分廣泛,DOM上的各種事件。在Ajax大規模應用之后,異步請求更得到廣泛的認同,而Ajax亦是基於事件機制的。

  在Rhino中,文件讀取等操作,均是同步操作進行的。在這類單線程的編程模型下,如果采用同步機制,無法與PHP之類的服務端腳本語言的成熟度媲美,性能也沒有值得可圈可點的部分。

  直到Ryan Dahl在2009年推出Node.js后,后端JavaScript才走出其迷局。

 

  Node.js的推出,我覺得該變了兩個狀況:

  1. 統一了前后端JavaScript的編程模型。
  2. 利用事件機制充分利用用異步IO突破單線程編程模型的性能瓶頸,使得JavaScript在后端達到實用價值。

 

  有了第二次瀏覽器大戰中的佼佼者V8的適時助力,使得Node.js在短短的兩年內達到可觀的運行效率,並迅速被大家接受。這一點從Node.js項目在Github上的流行度和NPM上的庫的數量可見一斑。

 


 

 

Node.js事件:

  Node.js中,所有異步的I/O操作,在完成的時候都會發送一個事件到事件隊列中。

  Node.js中的許多對象也都會分發事件,比如:

    1. net.Server 對象會在每次有新鏈接時分發一個事件;

    2. fs.readStream 對象會在文件被打開的時候分發一個事件;

    3.。。。。。。

 

  所有這些產生事件的對象都是 event.EventEmitter (事件監聽/發射器)的實例。我們可以通過“ require('events') ”來訪問該模塊。

   Event模塊(event.EventEmitter)是一個簡單的事件監聽器模式的實現,具有 addListener 、 on 、 once 、 removelistener 、 removeAllListener 、 emit 等基本的事件監聽模式的方法實現。

  它與前端DOM樹上的事件並不相同,因為它不存在事件冒泡,逐層捕獲等屬於DOM的事件行為,也沒有preventDefault()、stopPropagation()、stopImmediatePropagation()等處理事件傳遞的方法。

 

  從另一個角度來看,事件偵聽器模式也是一種事件鈎子(hook)的機制,利用事件鈎子導出內部數據或狀態給外部調用者。

  Node.js中的很多對象,大多具有黑盒的特點,功能點較少,如果不通過事件鈎子的形式,對象運行期間的中間值或內部狀態,是我們無法獲取到的。

  這種通過事件鈎子的方式,可以使編程者不用關注組件是如何啟動和執行的,只需關注在需要的事件點上即可。

 

  回顧我們之前創建HTTP服務器的Node.js代碼,小動更改,新建app.js代碼如下:

var http = require("http");

function onRequest(request, response){
    console.log("Request received.");
    response.writeHead(200, {"Content-Type" : "text/plain"});
    response.write("Hello World!");
    response.end();
}

http.createServer(onRequest).listen(88);

console.log("Server has started!");

  然后我們運行“node app.js”,訪問瀏覽器即可看到效果:

  

  

 

  而當我們刷新頁面時,服務端便會創建一個請求:

  

 

  【分析:(大家不要死扣這部分內容,只需要大概了解Node.js中事件的特點 - “事件輪詢機制”就OK了。)】

  這段代碼中,我們使用函數onRequest封裝了請求的處理部分。當我們啟動它會立即輸出“Server started!”。在我們的瀏覽器訪問http://128.0.0.1:88時,會顯示消息“Request received。”

  這兩個代碼的主要區別是,前者將處理部分寫在 http.createServer 中,按照傳統思路,啟動服務器后,遇到這段代碼會去運行,如果運行時間很長,導致暫停,非常沒有效率。如果第二位用戶請求的服務器,而它仍然在服務第一個請求,那第二個請求只能回答第一個完成后才能應答,這就是堵塞式Socket IO的問題。

  Node.js的通過一個低級別的C / C + +層將異步執行有關IO的操作,一旦監聽到請求, Node.js將執行您作為參數傳遞到I / O操作函數的回調函數,如上面的onRequest。這個異步操作關鍵是基於事件輪詢機制

  關於事件輪詢機制,內部覆蓋比較廣,我也就不增加本文篇幅了。如果感興趣,大家可以自行百度,網上有案例講解。

 


 

 

注冊並發射自定義Node.js事件:

  我們現在要做的實例就是:使用Node.js注冊一個用戶自定義事件,然后再使用Node.js發射這個自定義事件。

   步驟1:新建app.js,代碼如下:

 1 var EventEmitter = require('events').EventEmitter;     // 引入事件模塊
 2 
 3 var event = new EventEmitter();     // 實例化事件模塊
 4 
 5 // 注冊事件(customer_event)
 6 event.on('customer_event', function() { 
 7     console.log('customer_event has be occured : ' + new Date()); 
 8 }); 
 9 
10 setInterval(function() { 
11     event.emit('customer_event');     // 發射(觸發)事件(customer_event)
12 }, 500); 

  上述代碼中,我們首先使用require引入事件模塊的EventEmitter(注冊/發射器)。

  然后,我們實例化EventEmitter對象,存入到本地變量event中。

  然后,我們使用event對象的on函數,注冊一個名為“customer_event”的自定義事件,事件的動作為輸出一段信息。

  最后,我們使用setInterval函數,每500ms循環調用event對象的emit函數,來發射(觸發)我們自定義的“customer_event”事件。

 

  步驟2:運行“node app.js”,效果如下:

 

  這個應該很好理解吧。

  到此為止,我們知道了:使用EventEmitter對象的on注冊事件,然后使用對象的emit發射事件。

 


 

EventEmitter介紹:

  events 模塊只提供了一個對象: events.EventEmitter。EventEmitter 的核心就是事件發射事件監聽器功能的封裝。

  我們直接上例子吧。邊看邊說。

 

  步驟1:新建app.js主程序,代碼如下:

 1 var EventEmitter = require('events').EventEmitter;     // 引入事件模塊
 2 
 3 var event = new EventEmitter();     // 實例化事件模塊
 4 
 5 // 注冊事件(sayHello)
 6 event.on('sayHello', function(param1, param2) { 
 7     console.log('Hello1 : ', param1, param2); 
 8 }); 
 9 
10 // 再次注冊事件(sayHello)
11 event.on('sayHello', function(param1, param2) { 
12     console.log('Hello2 : ', param1, param2); 
13 }); 
14 
15 event.emit('sayHello', 'GuYing', '1996');     // 發射(觸發)事件(sayHello)

  上述代碼中,有兩點值得介紹的是事件參數,其次您可能覺得比較別扭的是,這個事件注冊了兩次!

  EventEmitter 的每一個事件都是由一個事件名若干個參數組成。事件名是一個字符串,通常表達一定的語義。對於每個事件,EventEmitter 支持若干個事件監聽器

  當事件發射時,注冊到這個事件的事件監聽器被依次調用,事件參數作為回調函數參數傳遞。

 

  步驟2:運行“node app.js”,執行效果如下:

 

  以上例子中,emitter 為事件 someEvent 注冊了兩個事件監聽器,然后發射了 someEvent 事件。

  運行結果中可以看到兩個事件監聽器回調函數被先后調用。 這就是EventEmitter最簡單的用法。

  


 

 

EventEmitter常用的API:

  EventEmitter.on(event, listener)、emitter.addListener(event, listener) 為指定事件注冊一個監聽器,接受一個字 符串 event 和一個回調函數 listener。

server.on('connection', function (stream) {
  console.log('someone connected!');
});

 

  EventEmitter.emit(event, [arg1], [arg2], [...]) 發射 event 事件,傳 遞若干可選參數到事件監聽器的參數表。

  EventEmitter.once(event, listener) 為指定事件注冊一個單次監聽器,即 監聽器最多只會觸發一次,觸發后立刻解除該監聽器。

server.once('connection', function (stream) {
  console.log('Ah, we have our first user!');
});

 

  EventEmitter.removeListener(event, listener) 移除指定事件的某個監聽 器,listener 必須是該事件已經注冊過的監聽器。

var callback = function(stream) {
  console.log('someone connected!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);

 

  EventEmitter.removeAllListeners([event]) 移除所有事件的所有監聽器, 如果指定 event,則移除指定事件的所有監聽器。

 


 

 

error事件:

  EventEmitter 定義了一個特殊的事件 error,它包含了"錯誤"的語義,我們在遇到 異常的時候通常會發射 error 事件。

  當 error 被發射時,EventEmitter 規定如果沒有響 應的監聽器,Node.js 會把它當作異常,退出程序並打印調用棧。

  我們一般要為會發射 error 事件的對象設置監聽器,避免遇到錯誤后整個程序崩潰。例如:

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

  運行時會顯示以下錯誤:

node.js:201 
throw e; // process.nextTick error, or 'error' event on first tick 
^ 
Error: Uncaught, unspecified 'error' event. 
at EventEmitter.emit (events.js:50:15) 
at Object.<anonymous> (/home/byvoid/error.js:5:9) 
at Module._compile (module.js:441:26) 
at Object..js (module.js:459:10) 
at Module.load (module.js:348:31) 
at Function._load (module.js:308:12) 
at Array.0 (module.js:479:10) 
at EventEmitter._tickCallback (node.js:192:40) 

 


 

 

繼承EventEmitter:

  大多數時候我們不會直接使用 EventEmitter,而是在對象中繼承它。包括 fs、net、 http 在內的,只要是支持事件響應的核心模塊都是 EventEmitter 的子類。

  為什么要這樣做呢?原因有兩點:

  首先,具有某個實體功能的對象實現事件符合語義, 事件的監聽和發射應該是一個對象的方法。

  其次JavaScript 的對象機制是基於原型的,支持 部分多重繼承,繼承 EventEmitter 不會打亂對象原有的繼承關系。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM