大家先來看一道題:
a.onclick = function(){
setTimeout(function() {
//do something ...
},0);
};
JavaScript API 文檔明確定義:setTimeout的第二個參數意義為隔多少毫秒后,回調方法就會被執(zhí)行。那么可以推斷出:這里設成0毫秒,就立即被執(zhí)行了。
—————— 既然立即執(zhí)行,為什么這段代碼的作者為什么要 舍近求遠?難道作者寫代碼的時候喝醉了?
顯然不是!
這個問題 David Flanagan 在《Javascript 權威指南》中有闡述:當 setTimeout 的延遲時間設置為 0 的時候,回調函數不會馬上執(zhí)行,而是進入隊列。
David Flanagan 在《Javascript權威指南》里并沒有詳細描述Javascript隊列的具體運作方式,現(xiàn)在我來大致說一下這個問題。
JavaScript 引擎是單線程模式運行的,瀏覽器無論在什么時候都只且只有一個JavaScript線程在運行程序。
要說清楚這些問題,還得從瀏覽器內核處理定時器(setTimeout、setInterval)和響應瀏覽器事件說起。
瀏覽器內核實現(xiàn)允許多個線程異步執(zhí)行,這些線程在內核制控下相互配合以保持同步。假設某一瀏覽器內核的實現(xiàn)至少有三個常駐線程:javascript引擎線程、界面渲染線程、瀏覽器事件觸發(fā)線程。除些以外,也有一些執(zhí)行完就終止的線程:如Http請求線程等,這些異步線程都會產生不同的異步事件。
下面通過一個圖來闡明單線程的JavaScript引擎與另外那些線程是怎樣互動通信的。雖然每個瀏覽器內核 (流行瀏覽器內核有:Trident[IE內核]、Gecko[Firefox內核]、Presto[Opera內核]、Webkit[Chrome、Safari] 等) 實現(xiàn)細節(jié)不同,但這其中的調用原理都是大同小異。
JavaScript定時機制、以及瀏覽器渲染機制 淺談 javascript-thread
瀏覽器中的JavaScript引擎是基于事件驅動的。這里的事件可看作是瀏覽器派給它的各種任務,如調用setTimeout 添加一個任務,也可來自瀏覽器內核的其它線程,如界面元素鼠標點擊事件、定時觸發(fā)器時間到達通知、異步請求狀態(tài)變更通知等。
從代碼角度看來任務實體就是各種回調函數,JavaScript引擎一直等待著任務隊列中任務的到來。由于單線程關系,這些任務得進行排隊,一個接著一個被引擎處理。
圖形界面渲染線程:
該線程負責渲染瀏覽器界面HTML元素,當界面需要重繪或由于某種操作引發(fā)回流時,該線程就會執(zhí)行。
我今天想重點解釋JavaScript定時機制,但這時有必要說說渲染線程,因為該線程與JavaScript引擎線程是互斥的!這一點Yahoo 前端攻城師在博客上有一篇詳細文章描述這個問題。這也容易理解:因為JavaScript腳本是可操縱DOM元素,在修改這些元素屬性同時渲染界面,那么渲染線程前后獲得的元素數據就可能不一致了。
在JavaScript引擎運行腳本期間,瀏覽器渲染線程都是處于掛起狀態(tài)的,也就是說被”凍結”了(題外話:YSlow優(yōu)化網頁建議指南指出:js文件要放在html內容的下面,就是因為加載 js 的時候,會阻塞 DOM樹的構建。這一點可以再Yahoo工程師的前端優(yōu)化方案里面看到)。
GUI事件觸發(fā)線程:
JavaScript腳本的執(zhí)行不影響html元素事件的觸發(fā),在Time1時間段內,用戶點擊鼠標鍵,點擊事件被瀏覽器事件觸發(fā)線程捕捉后,形成一個鼠標點擊事件,對于JavaScript引擎線程來說,這事件是由其它線程異步傳到任務隊列尾的,由于引擎正在處理 Time1 時的任務,這個鼠標點擊事件就會排隊。
定時觸發(fā)線程:
注意這里的瀏覽器模型定時計數器并不是由JavaScript引擎計數的,因為JavaScript引擎是單線程的,如果處于阻塞線程狀態(tài)就不能計時,它必須依賴外部來計時并觸發(fā)定時,所以隊列中的定時事件也是異步事件。
更多信息請查看IT技術專欄