目錄:
前言:
今天事兒太多了,沒有發太多的東西。不好意思了各位。
本篇主要介紹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的推出,我覺得該變了兩個狀況:
- 統一了前后端JavaScript的編程模型。
- 利用事件機制充分利用用異步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 不會打亂對象原有的繼承關系。