国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 編程 > JavaScript > 正文

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

2019-11-20 08:55:04
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

前言

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

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

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

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

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

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

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

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

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 copyconsole.log("end"); 

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

node.js的事件API

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

事件的監(jiān)聽(tīng):

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í)候我們可以傳入多個(gè)參數(shù),第一個(gè)參數(shù)表示事件的名稱(chēng),其后的參數(shù)表示傳入的參數(shù),這些參數(shù)會(huì)被傳入到事件的回調(diào)函數(shù)中。

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

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

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

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

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

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

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

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通過(guò)異步機(jī)制使請(qǐng)求之間無(wú)阻塞,達(dá)到并行請(qǐng)求的目的,有效的調(diào)用下層資源。但是,這個(gè)場(chǎng)景中的問(wèn)題是對(duì)于多個(gè)事件響應(yīng)結(jié)果的協(xié)調(diào)并非被Node.js原生優(yōu)雅地支持。

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

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

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

為解決這類(lèi)問(wèn)題,我曾寫(xiě)作一個(gè)模塊來(lái)實(shí)現(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也是一個(gè)簡(jiǎn)單的事件偵聽(tīng)者模式的實(shí)現(xiàn),由于底層實(shí)現(xiàn)跟Node.js的EventEmitter不同,無(wú)法合并進(jìn)Node.js中。但是卻提供了比EventEmitter更強(qiáng)大的功能,且API保持與EventEmitter一致,與Node.js的思路保持契合,并可以適用在前端中。
這里的all方法是指?jìng)陕?tīng)完profile、timeline、skin三個(gè)方法后,執(zhí)行回調(diào)函數(shù),并將偵聽(tīng)接收到的數(shù)據(jù)傳入。

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

如果通過(guò)Jscex編寫(xiě),將會(huì)是以下形式:

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

利用事件隊(duì)列解決雪崩問(wèn)題

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

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

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

以上是一句數(shù)據(jù)庫(kù)查詢(xún)的調(diào)用,如果站點(diǎn)剛好啟動(dòng),這時(shí)候緩存中是不存在數(shù)據(jù)的,而如果訪問(wèn)量巨大,同一句SQL會(huì)被發(fā)送到數(shù)據(jù)庫(kù)中反復(fù)查詢(xún),影響到服務(wù)的整體性能。一個(gè)改進(jìn)是添加一個(gè)狀態(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是沒(méi)有數(shù)據(jù)服務(wù)的。所以這個(gè)時(shí)候引入事件隊(duì)列吧:

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

總結(jié)

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

發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 分宜县| 阜阳市| 馆陶县| 富顺县| 建平县| 安达市| 图们市| 青田县| 华安县| 澄迈县| 岳西县| 东兴市| 湘潭县| 乌兰县| 阳江市| 林甸县| 太仓市| 文昌市| 凌源市| 哈密市| 佛山市| 嵊州市| 海伦市| 从江县| 筠连县| 铁岭县| 砚山县| 台北市| 阿城市| 翁牛特旗| 无极县| 监利县| 尼木县| 西安市| 高雄市| 金坛市| 南昌县| 定边县| 磐石市| 屯昌县| 清远市|