Node.js事件驅(qū)動(dòng)
Node.js事件驅(qū)動(dòng)實(shí)現(xiàn)概覽
雖然在ECMAScript的標(biāo)準(zhǔn)里并沒(méi)有(也沒(méi)有必要)明確規(guī)定“事件”,但是在瀏覽器中,事件作為一個(gè)極為重要的機(jī)制,給予JavaScript響應(yīng)用戶(hù)操作與DOM變化的能力;在Node.js中,異步事件驅(qū)動(dòng)模型則是其高并發(fā)能力的基礎(chǔ)。
學(xué)習(xí)JavaScript也需要了解它的運(yùn)行平臺(tái),為了更好的理解JavaScript的事件模型,我打算從Node及瀏覽器引擎源碼入手,分析其底層實(shí)現(xiàn),并將我的分析整理為一系列博文;一方面作為筆記,另一方面也希望能與大家交流,分析和理解有疏漏偏頗之處,還望各位斧正。
簡(jiǎn)述事件驅(qū)動(dòng)模型
解釋JavaScript事件模型本身的好文章已經(jīng)很多了,可以說(shuō)這已經(jīng)是一個(gè)說(shuō)爛了的話(huà)題,這里我只簡(jiǎn)單寫(xiě)一下,并且提供一些好文章的鏈接。
程序如何響應(yīng)事件
我們的程序響應(yīng)外部的事件有如下兩種方式:
中斷
操作系統(tǒng)處理鍵盤(pán)等硬件輸入就是通過(guò)中斷來(lái)進(jìn)行的,這個(gè)方式的好處是即使沒(méi)有多線(xiàn)程,我們也可以放心地執(zhí)行我們的代碼,CPU收到中斷信號(hào)之后自動(dòng)地轉(zhuǎn)去執(zhí)行相應(yīng)的中斷處理程序,處理完成后會(huì)恢復(fù)原來(lái)的代碼的執(zhí)行環(huán)境繼續(xù)執(zhí)行。這種方式需要硬件的支持,一般來(lái)說(shuō)都會(huì)被操作系統(tǒng)封裝起來(lái)。
輪詢(xún)
循環(huán)檢測(cè)是否有事件發(fā)生,如果有就去執(zhí)行相應(yīng)的處理程序。這在底層和上層的開(kāi)發(fā)中都有應(yīng)用。
Windows窗口程序就需要在主線(xiàn)程中寫(xiě)下如下代碼,通常稱(chēng)做消息循環(huán):
MSG msg = { }; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
消息循環(huán)不斷檢測(cè)是否有消息(用戶(hù)的UI操作、系統(tǒng)消息等)出現(xiàn),有的話(huà)就分發(fā)消息,調(diào)用相應(yīng)的回調(diào)函數(shù)進(jìn)行處理。
輪詢(xún)方式的一個(gè)缺點(diǎn)就是:如果在主線(xiàn)程的消息循環(huán)里進(jìn)行耗時(shí)操作,程序就無(wú)法及時(shí)響應(yīng)新的消息。這在JavaScript中表現(xiàn)明顯,以后還會(huì)提到這一點(diǎn),并探討其解決方案。
然而JavaScript中并沒(méi)有類(lèi)似消息循環(huán)代碼,我們只是簡(jiǎn)單地注冊(cè)事件,然后等待被調(diào)用。這是因?yàn)闉g覽器、Node作為執(zhí)行平臺(tái),已經(jīng)將event loop實(shí)現(xiàn)了,JavaScript代碼不需要介入到這個(gè)過(guò)程中,只需要作為被調(diào)用者安靜地等待即可。
Node中的event loop
通過(guò)Node源碼看event loop的實(shí)現(xiàn)
Node采用V8作為JavaScript的執(zhí)行引擎,同時(shí)使用libuv實(shí)現(xiàn)事件驅(qū)動(dòng)式異步I/O。其事件循環(huán)就是采用了libuv的默認(rèn)事件循環(huán)。
在src/node.cc中,
Environment* env = CreateEnvironment( node_isolate, uv_default_loop(), context, argc, argv, exec_argc, exec_argv);
這段代碼建立了一個(gè)node執(zhí)行環(huán)境,可以看到第三行的uv_default_loop(),這是libuv庫(kù)中的一個(gè)函數(shù),它會(huì)初始化uv庫(kù)本身以及其中的default_loop_struct,并返回一個(gè)指向它的指針default_loop_ptr。
之后,Node會(huì)載入執(zhí)行環(huán)境并完成一些設(shè)置操作,然后啟動(dòng)event loop:
bool more; do { more = uv_run(env->event_loop(), UV_RUN_ONCE); if (more == false) { EmitBeforeExit(env); // Emit `beforeExit` if the loop became alive either after emitting // event, or after running some callbacks. more = uv_loop_alive(env->event_loop()); if (uv_run(env->event_loop(), UV_RUN_NOWAIT) != 0) more = true; } } while (more == true); code = EmitExit(env); RunAtExit(env); ...
more用來(lái)標(biāo)識(shí)是否進(jìn)行下一輪循環(huán)。
env->event_loop()會(huì)返回之前保存在env中的default_loop_ptr,uv_run函數(shù)將以指定的UV_RUN_ONCE模式啟動(dòng)libuv的event loop。在這種模式下,uv_run會(huì)至少處理一個(gè)事件:這意味著,如果當(dāng)前事件隊(duì)列中沒(méi)有需要處理的I/O事件,uv_run會(huì)阻塞住,直到有I/O事件需要處理,或者下一個(gè)定時(shí)器時(shí)間到。如果當(dāng)前沒(méi)有I/O事件也沒(méi)有定時(shí)器事件,則uv_run返回false。
接下來(lái)Node會(huì)根據(jù)more的情況決定下一步操作:
如果more為true,則繼續(xù)運(yùn)行下一輪loop。
如果more為false,說(shuō)明已經(jīng)沒(méi)有等待處理的事件了,EmitBeforeExit(env);觸發(fā)進(jìn)程的'beforeExit'事件,檢查并處理相應(yīng)的處理函數(shù),完成后直接跳出循環(huán)。
最后觸發(fā)'exit'事件,執(zhí)行相應(yīng)的回調(diào)函數(shù),Node運(yùn)行結(jié)束,后面會(huì)進(jìn)行一些資源釋放操作。
在libuv中,定時(shí)器事件是直接在event loop中處理的,而I/O事件則分為兩類(lèi):
Network I/O是使用系統(tǒng)提供的非阻塞式I/O解決方案,例如在Linux上使用epoll,windows上使用IOCP。
文件操作和DNS操作沒(méi)有(很好的)系統(tǒng)解決方案,因此libuv自建了線(xiàn)程池,在其中進(jìn)行阻塞式I/O。
另外我們也可以將自定義的函數(shù)拋到線(xiàn)程池中運(yùn)行,在運(yùn)行結(jié)束后主線(xiàn)程會(huì)執(zhí)行相應(yīng)的回調(diào)函數(shù),不過(guò)Node并沒(méi)有將這一項(xiàng)功能加入到JavaScript中,也就是說(shuō)只用原生Node是無(wú)法在JavaScript中開(kāi)啟新的線(xiàn)程進(jìn)行并行執(zhí)行的。
以上所述就是本文的全部?jī)?nèi)容了,希望大家能夠喜歡。
- 理解 Node.js 事件驅(qū)動(dòng)機(jī)制的原理
- js事件驅(qū)動(dòng)機(jī)制 瀏覽器兼容處理方法
- 淺談javascript基礎(chǔ)之客戶(hù)端事件驅(qū)動(dòng)
- 快速掌握Node.js事件驅(qū)動(dòng)模型
- 詳解Javascript事件驅(qū)動(dòng)編程
- Node.js中的事件驅(qū)動(dòng)編程詳解
- 深入理解javaScript中的事件驅(qū)動(dòng)
- 你必須知道的Javascript知識(shí)點(diǎn)之"單線(xiàn)程事件驅(qū)動(dòng)"的使用
- JScript面向事件驅(qū)動(dòng)的編程
- 淺談JS和Nodejs中的事件驅(qū)動(dòng)
相關(guān)文章
npm?i報(bào)錯(cuò)以及解決方案實(shí)戰(zhàn)案例
npm在前端開(kāi)發(fā)流程中提供了非常完善的自動(dòng)化工具鏈,但是同樣由于其復(fù)雜性導(dǎo)致有很多奇奇怪怪的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于npm?i報(bào)錯(cuò)以及解決方案的相關(guān)資料,需要的朋友可以參考下2022-07-07從零學(xué)習(xí)node.js之express入門(mén)(六)
相信大家都知道Express是一個(gè)簡(jiǎn)潔而靈活的 node.js Web應(yīng)用框架, 提供了一系列強(qiáng)大特性幫助你創(chuàng)建各種 Web 應(yīng)用,和豐富的 HTTP 工具。下面這篇文章主要介紹了node.js中express的入門(mén)知識(shí),需要的朋友可以參考下。2017-02-02node.js突破nginx防盜鏈機(jī)制,下載圖片案例分析
這篇文章主要介紹了node.js突破nginx防盜鏈機(jī)制,下載圖片的方法,結(jié)合具體案例形式分析了防盜鏈的相關(guān)原理與node.js使用axios庫(kù)下載防盜鏈圖片的相關(guān)操作技巧,需要的朋友可以參考下2023-04-04Node.js+Express.js+TS實(shí)現(xiàn)簡(jiǎn)單圖床腳本
在這篇博客文章中,我將介紹如何使用 TypeScript 和 Express 框架來(lái)編寫(xiě)一個(gè)簡(jiǎn)單的圖床腳本,可以將本地圖片上傳到服務(wù)器,并返回圖片的 URL,這樣,你就可以在 Markdown 文檔中方便地引用圖片,而不用擔(dān)心圖片的存儲(chǔ)和管理問(wèn)題2023-10-10node.js實(shí)現(xiàn)端口轉(zhuǎn)發(fā)
這篇文章主要為大家詳細(xì)介紹了node.js實(shí)現(xiàn)端口轉(zhuǎn)發(fā)的關(guān)鍵代碼,感興趣的小伙伴們可以參考一下2016-04-04nodejs?express路由匹配控制及Router模塊化使用詳解
這篇文章主要為大家介紹了nodejs?express路由匹配控制及Router模塊化使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10