上一篇博文提到,在Node中timer並非經過新開線程來實現的,而是直接在event loop中完成。下面經過幾個JavaScript的定時器示例以及Node相關源碼來分析在Node中,timer功能究竟是怎麼實現的。node
不管是Node仍是瀏覽器中,都有setTimeout和setInterval這兩個定時器函數,而且其工做特色基本相同,所以下面僅以Node爲例進行分析。segmentfault
咱們知道,JavaScript中的定時器並不一樣於計算機底層的定時中斷。中斷到來時,當前執行代碼會被打斷,轉去執行定時中斷處理函數。而JavaScript的定時器到時,若是當前執行線程沒有正在執行的代碼,則執行相應的回調函數;若是當前有代碼在執行中,JavaScript引擎既不會中斷當前代碼轉去執行回調,也不會開新的線程執行回調,而是當前代碼執行完畢以後纔去處理。瀏覽器
console.time('A') setTimeout(function () { console.timeEnd('A'); }, 100); var i = 0; for (; i < 100000; i++) { }
執行上面的代碼,能夠看到最終輸出的時間並非100ms左右,而是數秒。這說明在循環完成以前,定時回調函數確實沒有被執行,而是推遲到了循環結束。實際上在JavaScript代碼執行中,全部的事件都沒法獲得處理,必須等到當前代碼所有完成,才能去處理新的事件。這就是爲何在瀏覽器中運行耗時JavaScript代碼時,瀏覽器會失去響應。爲了應對這種狀況,咱們能夠採起Yielding Processes的技巧,將耗時的代碼分紅小塊(chunks),每處理完一塊就執行一次setTimeout
,約定在一小段時間後才處理下一塊,而在這段空閒時間裏,瀏覽器/Node能夠去處理排隊中的事件。函數
在JavaScript 高級程序設計 第三版
第22章高級技巧
中對高級定時器以及Yielding Processes有較詳細的討論。oop
上一篇博文提到Node會調用libuv的uv_run
函數啓動default_loop_ptr
進行事件調度,default_loop_ptr
指向一個uv_loop_t
類型的變量default_loop_struct
。Node啓動時會調用uv_loop_init(&default_loop_struct)
對其進行初始化,uv_loop_init
函數節選以下:ui
int uv_loop_init(uv_loop_t* loop) { ... loop->time = 0; uv_update_time(loop); ... }
能夠看到loop
的time
字段先被賦值爲0,以後調用uv_update_time
函數,這會將最新的計數時間賦給loop.time
。spa
初始化完成以後,default_loop_struct.time
就有了一個初始值,與時間有關的操做都會與此值進行比較從而肯定是否調用相應回調函數。線程
前面提到uv_run
函數就是libuv庫實現event loop的核心部分,下面是其流程圖:設計
這裏簡述一下上面與定時器相關的邏輯:code
loop
的time
字段,這個字段標誌着當前loop
概念下的「如今」;loop
是否alive,也就是說檢查loop
中是否還有須要處理的任務(handlers/requests
),若是沒有就沒必要循環了;Node會一直調用uv_run
直到loop
再也不alive。
Node中有一個TimerWrap
類,被註冊爲Node內部的timer_wrap
模塊。
NODE_MODULE_CONTEXT_AWARE_BUILTIN(timer_wrap, node::TimerWrap::Initialize)
其中TimerWrap
類基本上就是對uv_timer_t
的一個直接封裝,NODE_MODULE_CONTEXT_AWARE_BUILTIN
是Node用於註冊built-in模塊的宏。
通過這一步操做,JavaScript就能夠拿到這個模塊進行操做了。src/lib/timers.js
文件使用JavaScript的形式把timer_wrap
的功能封裝起來,並導出了exports.setTimeout, exports.setInterval, exports.setImmediate
等函數。
上一篇提到Node啓動時會載入執行環境LoadEnvironment(env)
,這個函數中很是重要的一步就是載入src/node.js
並執行,src/node.js
會載入指定的模塊並初始化global
和process
。固然,setTimeout
等函數也會被src/node.js
綁定到global
對象上。
至此,setTimeout/setInterval
這類定時器函數已經能夠爲JavaScript所用了。