欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Node.js中的事件機(jī)制

 更新時間:2016年09月22日 09:21:03   作者:yangnianbing110  
Node.js能夠在眾多的后端JavaScript技術(shù)之中脫穎而出,正是因其基于事件的特點而受到歡迎,所以這篇文章小編給大家詳細(xì)介紹了Node.js中的事件機(jī)制,本文介紹的很詳細(xì),對大家的理解和學(xué)習(xí)很有幫助,下面來一起看看吧。

前言

在前端編程中,事件的應(yīng)用十分廣泛,DOM上的各種事件。在Ajax大規(guī)模應(yīng)用之后,異步請求更得到廣泛的認(rèn)同,而Ajax亦是基于事件機(jī)制的。

通常js給我們的第一印象就是運行在客戶端瀏覽器上面的腳本,通過node.js我們可以在服務(wù)端運行javascript.

node.js是基于單線程無阻塞異步式的I/O,異步式的I/O指的是當(dāng)遇到I/O操作的時候,線程不阻塞而是進(jìn)行下面的操作,那么I/O操作完成之后,線程時如何知道該操作完成的呢?

當(dāng)操作完成耗時的I/O操作之后,會以事件的形式通知I/O操作的線程完成,線程會在特定的時候來處理這個事件,進(jìn)行下一步的操作,為了完成異步I/O,線程必須有事件循環(huán)的機(jī)制,不停的堅持是否有沒有完成的事件,依次完成這些事件的處理。

而對于阻塞式I/O,線程遇到耗時的I/O操作會停止繼續(xù)執(zhí)行,等待操作的完成,這個時候線程就不能接受其他的操作請求,為了提供吞吐量,必須創(chuàng)建多個線程,每個線程去響應(yīng)一個客戶的請求,但是同一時間,一個cpu核心上面只能運行一個線程,多個線程要想執(zhí)行就必須在不同的線程之間進(jìn)行切換。

因此node.js少了多線程中線程的創(chuàng)建,以及線程的切換的開銷,線程切換的代價是非常大的,需要為其分配內(nèi)存,列入調(diào)度,同時在線程切換的時候需要執(zhí)行內(nèi)存換頁等等操作,采用單線程的方式就可以減少這些操作。但是這種編程方式也有缺點,不符合人們的設(shè)計思維。

node.js是基于事件的模式來實現(xiàn)異步I/O的,當(dāng)其啟動之后會不停的遍歷是否有為完成的事件,然后進(jìn)行執(zhí)行,執(zhí)行完成之后會以另外一個事件的形式通知線程,本操作已經(jīng)完成,這個事件又會被添加到未完成的事件列表中,線程在接下來的某個時刻遍歷到這個事件然后進(jìn)行執(zhí)行,在這種機(jī)制中,需要將一個大的任務(wù)分成一個個小的事件,node.js也適合處理一些高I/O,低邏輯的場景。

下面的例子演示異步的文件讀取:

var fs = require('fs'); 
fs.readFile('file.txt', 'utf-8', function(err, data) { 
if (err) { 
<span style="white-space:pre"> </span>console.error(err); 
} else { 
<span style="white-space:pre"> </span>console.log(data); 
} 
}); 
[javascript] view plain copy
console.log("end"); 

如上fs.readFile異步讀取文件,之后流程就會繼續(xù)走,并不會等待其讀取完文件,當(dāng)文件讀取完畢之后,會發(fā)布一個事件,執(zhí)行線程遍歷到該事件就會去執(zhí)行對應(yīng)的操作,這里是執(zhí)行相應(yīng)的回調(diào)函數(shù),例子中字符串end會比文件內(nèi)容先打印出來。

node.js的事件API

events.EventEmitter:EventEmitter對node.js中的事件發(fā)射與事件監(jiān)聽功能提供了封裝,每個事件由一個標(biāo)識事件名的字符串和對應(yīng)的操作組成。

事件的監(jiān)聽:

var events = require("events"); 
var emitter = new events.EventEmitter(); 
 <span style="font-family: Arial, Helvetica, sans-serif;">emitter.on("eventName", function(){</span> 
  console.log("eventName事件發(fā)生") 
}) 

事件的發(fā)布:

emitter.emit("eventName"); 

發(fā)布事件的時候我們可以傳入多個參數(shù),第一個參數(shù)表示事件的名稱,其后的參數(shù)表示傳入的參數(shù),這些參數(shù)會被傳入到事件的回調(diào)函數(shù)中。

EventEmitter.once("eventName", listener) :為事件注冊一個只執(zhí)行一次的監(jiān)聽器,當(dāng)事件第一次發(fā)生并觸發(fā)監(jiān)聽器之后,該監(jiān)聽器就會解除,之后如果事件發(fā)生,該監(jiān)聽器不會執(zhí)行。

EventEmitter.removeListener(event, listener) :移除掉事件的監(jiān)聽器

EventEmitter.removeAllListeners(event) :移除掉事件的所有的監(jiān)聽器

EventEmitter.setMaxListeners(n) :node.js默認(rèn)單個事件最大的監(jiān)聽器個數(shù)是10,如果超過10會給予警告,這么做是為了防止內(nèi)存的溢出,我們可以更改這種限制設(shè)置為其他的數(shù)字,如果設(shè)置為0表示不進(jìn)行限制。

EventEmitter.listeners(event) :返回某個事件的監(jiān)聽器列表

多事件之間協(xié)作
在略微大一點的應(yīng)用中,數(shù)據(jù)與Web服務(wù)器之間的分離是必然的,如新浪微博、Facebook、Twitter等。這樣的優(yōu)勢在于數(shù)據(jù)源統(tǒng)一,并且可以為相同數(shù)據(jù)源制定各種豐富的客戶端程序。

以Web應(yīng)用為例,在渲染一張頁面的時候,通常需要從多個數(shù)據(jù)源拉取數(shù)據(jù),并最終渲染至客戶端。Node.js在這種場景中可以很自然很方便的同時并行發(fā)起對多個數(shù)據(jù)源的請求。

api.getUser("username", function (profile) {
 // Got the profile
});
api.getTimeline("username", function (timeline) {
 // Got the timeline
});
api.getSkin("username", function (skin) {
 // Got the skin
});

Node.js通過異步機(jī)制使請求之間無阻塞,達(dá)到并行請求的目的,有效的調(diào)用下層資源。但是,這個場景中的問題是對于多個事件響應(yīng)結(jié)果的協(xié)調(diào)并非被Node.js原生優(yōu)雅地支持。

為了達(dá)到三個請求都得到結(jié)果后才進(jìn)行下一個步驟,程序也許會被變成以下情況:

api.getUser("username", function (profile) {
 api.getTimeline("username", function (timeline) {
  api.getSkin("username", function (skin) {
   // TODO
  });
 });
});

這將導(dǎo)致請求變?yōu)榇羞M(jìn)行,無法最大化利用底層的API服務(wù)器。

為解決這類問題,我曾寫作一個模塊來實現(xiàn)多事件協(xié)作,以下為上面代碼的改進(jìn)版:

var proxy = new EventProxy();
proxy.all("profile", "timeline", "skin", function (profile, timeline, skin) {
 // TODO
});
api.getUser("username", function (profile) {
 proxy.emit("profile", profile);
});
api.getTimeline("username", function (timeline) {
 proxy.emit("timeline", timeline);
});
api.getSkin("username", function (skin) {
 proxy.emit("skin", skin);
});

EventProxy也是一個簡單的事件偵聽者模式的實現(xiàn),由于底層實現(xiàn)跟Node.js的EventEmitter不同,無法合并進(jìn)Node.js中。但是卻提供了比EventEmitter更強(qiáng)大的功能,且API保持與EventEmitter一致,與Node.js的思路保持契合,并可以適用在前端中。
這里的all方法是指偵聽完profile、timeline、skin三個方法后,執(zhí)行回調(diào)函數(shù),并將偵聽接收到的數(shù)據(jù)傳入。

最后還介紹一種解決多事件協(xié)作的方案,通過運行時編譯的思路(需要時也可在運行前編譯),將同步思維的代碼轉(zhuǎn)換為最終異步的代碼來執(zhí)行,可以在編寫代碼的時候通過同步思維來寫,可以享受到同步思維的便利寫作,異步執(zhí)行的高效性能。

如果通過Jscex編寫,將會是以下形式:

var data = $await(Task.whenAll({
 profile: api.getUser("username"),
 timeline: api.getTimeline("username"),
 skin: api.getSkin("username")
}));
// 使用data.profile, data.timeline, data.skin
// TODO

利用事件隊列解決雪崩問題

所謂雪崩問題,是在緩存失效的情景下,大并發(fā)高訪問量同時涌入數(shù)據(jù)庫中查詢,數(shù)據(jù)庫無法同時承受如此大的查詢請求,進(jìn)而往前影響到網(wǎng)站整體響應(yīng)緩慢。

那么在Node.js中如何應(yīng)付這種情景呢。

var select = function (callback) {
  db.select("SQL", function (results) {
   callback(results);
  });
 };

以上是一句數(shù)據(jù)庫查詢的調(diào)用,如果站點剛好啟動,這時候緩存中是不存在數(shù)據(jù)的,而如果訪問量巨大,同一句SQL會被發(fā)送到數(shù)據(jù)庫中反復(fù)查詢,影響到服務(wù)的整體性能。一個改進(jìn)是添加一個狀態(tài)鎖。

var status = "ready";
var select = function (callback) {
  if (status === "ready") {
   status = "pending";
   db.select("SQL", function (results) {
    callback(results);
    status = "ready";
   });
  }
 };

但是這種情景,連續(xù)的多次調(diào)用select發(fā),只有第一次調(diào)用是生效的,后續(xù)的select是沒有數(shù)據(jù)服務(wù)的。所以這個時候引入事件隊列吧:

var proxy = new EventProxy();
var status = "ready";
var select = function (callback) {
  proxy.once("selected", callback);
  if (status === "ready") {
   status = "pending";
   db.select("SQL", function (results) {
    proxy.emit("selected", results);
    status = "ready";
   });
  }
 };

這里利用了EventProxy對象的once方法,將所有請求的回調(diào)都壓入事件隊列中,并利用其執(zhí)行一次就會將監(jiān)視器移除的特點,保證每一個回調(diào)只會被執(zhí)行一次。對于相同的SQL語句,保證在同一個查詢開始到結(jié)束的時間中永遠(yuǎn)只有一次,在這查詢期間到來的調(diào)用,只需在隊列中等待數(shù)據(jù)就緒即可,節(jié)省了重復(fù)的數(shù)據(jù)庫調(diào)用開銷。由于Node.js單線程執(zhí)行的原因,此處無需擔(dān)心狀態(tài)問題。這種方式其實也可以應(yīng)用到其他遠(yuǎn)程調(diào)用的場景中,即使外部沒有緩存策略,也能有效節(jié)省重復(fù)開銷。此處也可以用EventEmitter替代EventProxy,不過可能存在偵聽器過多,引發(fā)警告,需要調(diào)用setMaxListeners(0)移除掉警告,或者設(shè)更大的警告閥值。

總結(jié)

以上就是關(guān)于Node.js中事件機(jī)制的全部內(nèi)容,希望這篇文章對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。

相關(guān)文章

  • Linux?Ubuntu升級nodejs版本的簡單步驟

    Linux?Ubuntu升級nodejs版本的簡單步驟

    Node.js是一種對應(yīng)于JavaScript運行時環(huán)境的編程語言,這篇文章主要給大家介紹了關(guān)于Linux?Ubuntu升級nodejs版本的簡單步驟,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • nodejs實現(xiàn)HTTPS發(fā)起POST請求

    nodejs實現(xiàn)HTTPS發(fā)起POST請求

    這篇文章主要介紹了nodejs實現(xiàn)HTTPS發(fā)起POST請求的實例代碼,非常的簡單實用,有需要的小伙伴可以參考下。
    2015-04-04
  • 手把手教你實現(xiàn) Promise的使用方法

    手把手教你實現(xiàn) Promise的使用方法

    這篇文章主要介紹了手把手教你實現(xiàn) Promise的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • nodejs如何獲取指定路徑下所有的文件夾名或類型

    nodejs如何獲取指定路徑下所有的文件夾名或類型

    這篇文章主要介紹了nodejs如何獲取指定路徑下所有的文件夾名或類型,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • 詳解nodejs通過響應(yīng)回寫的方式渲染頁面資源

    詳解nodejs通過響應(yīng)回寫的方式渲染頁面資源

    本篇文章主要介紹了詳解nodejs通過響應(yīng)回寫的方式渲染頁面資源,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • nodejs實現(xiàn)郵件發(fā)送服務(wù)實例分享

    nodejs實現(xiàn)郵件發(fā)送服務(wù)實例分享

    本文給大家講解的是簡單的使用nodejs搭建郵件發(fā)送服務(wù)的一個實例,非常的好用,有需要的小伙伴可以參考下
    2017-03-03
  • connect中間件session、cookie的使用方法分享

    connect中間件session、cookie的使用方法分享

    今天大象哥用了下connect的session和cookie,感覺還挺好用的,分享一下(里面坑挺多的,文檔寫的太模糊了,費了哥不少時間)。
    2014-06-06
  • Node.js 實現(xiàn)遠(yuǎn)程桌面監(jiān)控的方法步驟

    Node.js 實現(xiàn)遠(yuǎn)程桌面監(jiān)控的方法步驟

    這篇文章主要介紹了Node.js 實現(xiàn)遠(yuǎn)程桌面監(jiān)控的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • Koa從零搭建到Api實現(xiàn)項目的搭建方法

    Koa從零搭建到Api實現(xiàn)項目的搭建方法

    這篇文章主要介紹了Koa從零搭建到Api實現(xiàn)項目的搭建方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • 使用VS開發(fā) Node.js指南

    使用VS開發(fā) Node.js指南

    這篇文章主要介紹了使用VS開發(fā) Node.js的方法,主要是使用NTVS(Node.js Toolsfor Visual Studio)來實現(xiàn),有需要的小伙伴參考下
    2015-01-01

最新評論