在瀏覽器中,事件做爲一個極爲重要的機制,給予JavaScript響應用戶操做與DOM變化的能力;在Node.js中,事件驅動模型則是其高併發能力的基礎。html
學習JavaScript也須要了解它的運行平臺,爲了更好的理解JavaScript的事件模型,我打算從Node及瀏覽器引擎源碼入手,分析其底層實現,並將個人分析整理爲一系列博文;一方面做爲筆記,另外一方面也但願能與你們交流,分析和理解有疏漏偏頗之處,還望各位斧正。node
解釋JavaScript事件模型自己的好文章已經不少了,能夠說這已是一個說爛了的話題,這裏我只簡單寫一下,而且提供一些好文章的連接。segmentfault
咱們的程序響應外部的事件有以下兩種方式:windows
中斷
操做系統處理鍵盤等硬件輸入就是經過中斷來進行的,這個方式的好處是即便沒有多線程,咱們也能夠放心地執行咱們的代碼,CPU收到中斷信號以後自動地轉去執行相應的中斷處理程序,處理完成後會恢復原來的代碼的執行環境繼續執行。這種方式須要硬件的支持,通常來講都會被操做系統封裝起來。api
輪詢
循環檢測是否有事件發生,若是有就去執行相應的處理程序。這在底層和上層的開發中都有應用。
Windows窗口程序就須要在主線程中寫下以下代碼,一般稱作消息循環:瀏覽器
MSG msg = { }; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
消息循環不斷檢測是否有消息(用戶的UI操做、系統消息等)出現,有的話就分發消息,調用相應的回調函數進行處理。
輪詢方式的一個缺點就是:若是在主線程的消息循環裏進行耗時操做,程序就沒法及時響應新的消息。這在JavaScript中表現明顯,之後還會提到這一點,並探討其解決方案。多線程
然而JavaScript中並無相似消息循環代碼,咱們只是簡單地註冊事件,而後等待被調用。這是由於瀏覽器、Node做爲執行平臺,已經將event loop實現了,JavaScript代碼不須要介入到這個過程當中,只須要做爲被調用者安靜地等待便可。架構
知乎-關於瀏覽器處理事件的問題?匿名用戶的回答:這個回答裏圖很不錯,有助於理解event loop的工做原理;答案末尾有一些文章分享;併發
MDN - Concurrency model and Event Loop:MDN上對event loop的介紹。異步
Node採用V8做爲JavaScript的執行引擎,同時使用libuv實現事件驅動式異步I/O。其事件循環就是採用了libuv的默認事件循環。
在src/node.cc中,
Environment* env = CreateEnvironment( node_isolate, uv_default_loop(), context, argc, argv, exec_argc, exec_argv);
這段代碼創建了一個node執行環境,能夠看到第三行的uv_default_loop()
,這是libuv庫中的一個函數,它會初始化uv庫自己以及其中的default_loop_struct
,並返回一個指向它的指針default_loop_ptr
。
以後,Node會載入執行環境並完成一些設置操做,而後啓動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
用來標識是否進行下一輪循環。env->event_loop()
會返回以前保存在env
中的default_loop_ptr
,uv_run
函數將以指定的UV_RUN_ONCE
模式啓動libuv的event loop。在這種模式下,uv_run
會至少處理一個事件:這意味着,若是當前事件隊列中沒有須要處理的I/O事件,uv_run
會阻塞住,直到有I/O事件須要處理,或者下一個定時器時間到。若是當前沒有I/O事件也沒有定時器事件,則uv_run
返回false。
接下來Node會根據more
的狀況決定下一步操做:
若是more
爲true
,則繼續運行下一輪loop
。
若是more
爲false
,說明已經沒有等待處理的事件了,EmitBeforeExit(env);
觸發進程的'beforeExit'
事件,檢查並處理相應的處理函數,完成後直接跳出循環。
最後觸發'exit'
事件,執行相應的回調函數,Node運行結束,後面會進行一些資源釋放操做。
在libuv中,event loop會在每次循環的開始更新本身的time從而實現計時功能,而I/O事件則分爲兩類:
Network I/O是使用系統提供的非阻塞式I/O解決方案,例如在Linux上使用epoll,windows上使用IOCP。
文件操做和DNS操做沒有(很好的)系統解決方案,所以libuv自建了線程池,在其中進行阻塞式I/O。
另外咱們也能夠將自定義的函數拋到線程池中運行,在運行結束後主線程會執行相應的回調函數,不過Node並無將這一項功能加入到JavaScript中,也就是說只用原生Node是沒法在JavaScript中開啓新的線程進行並行執行的。
libuv Design Overview:關於libuv的架構及設計思路;
node child_process 'exit':Node的child_process 'exit'事件;
Node with threads:討論了使用libuv線程池異步運行JavaScript代碼。