首先咱們應該先知道瀏覽器內核渲染進程是由多線程組成的,其中主要包括如下幾個javascript
一、GUI渲染線程java
。主要負責渲染瀏覽器界面,解析HTML和CSS,構建DOM樹和RenderObject樹,佈局和繪製等瀏覽器
。當頁面須要重繪或者因爲某種操做引起頁面迴流時,該線程就會執行多線程
。注意,GUI渲染線程和JS引擎線程是互斥的,當JS引擎線程運行的時候,GUI渲染線程就會被掛起,GUI更新會被保存在一個隊列中,等待JS引擎空閒下來當即執行異步
二、JS引擎線程函數
。又稱爲JS內核,主要負責處理javascript腳本程序佈局
。JS引擎負責解析javascript腳本,運行代碼spa
。JS引擎一直等待着任務隊列中任務的到來,而後加以處理,一個Tab頁面中不管何時都只有一個js線程在執行js程序線程
。一樣,JS引擎線程和GUI渲染線程是互斥的,因此若是JS線程執行的時間過長,這樣會形成頁面的渲染不連貫,致使頁面渲染加載阻塞隊列
三、事件觸發線程
。該線程歸屬於瀏覽器而不是JS引擎線程,用來控制事件循環。(能夠這樣理解,JS引擎本身都忙不過來,須要瀏覽器另開線程協助)
。當JS引擎執行代碼塊 如setTimeOut(也可來自瀏覽器內核的其餘線程,如鼠標點擊,AJAX異步請求等)時,會把對應的任務添加到事件觸發線程中
。當對應的事件符合觸發條件被觸發時,該線程會把該事件添加到待處理的任務隊列的隊尾,等待JS引擎處理
。因爲JS引擎是單線程的,因此這些待處理任務隊列中的事件都得排隊等待JS引擎處理(JS引擎空閒時纔會去執行)
四、定時觸發器線程
。傳說中的setTimeOut和setInterval所在的線程
。瀏覽器定時計數器並非由javascript引擎計數的(由於JS引擎是單線程的,若是線程處於阻塞狀態就會影響計時的準確性)
。所以經過單獨的線程來計時並觸發定時(計時完畢後,添加到事件隊列中,等待JS引擎空閒後執行)
。須要注意,W3C在HTML標準中規定,setTimeOut小於4ms的時間間隔算爲4ms
五、異步http請求線程
。在XMLHttpRequest鏈接後是經過瀏覽器新開一個線程請求
。在檢測到狀態變動時,若是設置有回調函數,異步線程就產生狀態變動事件,將這個回調再放入事件隊列中,由js引擎空閒時執行
咱們都知道JS引擎是單線程的,這是由於JS的做用主要是與用戶互動,以及操做DOM,這決定了他只能是單線程的,不然會帶來不少同步的問題,假如JS有兩個線程,同一時間一個線程再某個DOM節點上添加東西,一個線程再刪除該DOM節點,這時候就會出現問題
而後咱們還須要理解一些概念:
.JS分爲同步任務和異步任務
1.同步任務都在主線程上執行,造成一個執行棧
2.主線程以外,事件觸發線程管理着一個任務隊列,只要異步任務有告終果,就會在任務隊列中添加一個事件
3.一旦執行棧中全部同步任務執行完畢,此時,JS引擎空閒,系統就會讀取任務隊列,將可執行的異步任務添加到可執行棧中,開始執行
如此循環上面的步驟
看到這裏咱們大概明白了,爲何setTimeOut推入的事件沒有在規定的時間執行,這是由於,當它推入到事件隊列中時,主線程尚未空閒,JS引擎還在執行主線程的任務,因此天然會有偏差
關於定時器:
上述事件循環機制的核心是:JS引擎線程和事件觸發線程
但事件上還有一些隱藏的細節,譬如,調用setTimeOut後,是如何等待特定時間後才添加到事件隊列中的?
是JS引擎檢測的麼?固然不是了。它是由定時器線程控制
爲何要單獨的定時器線程?由於JS引擎是單線程的,若是處於阻塞線程狀態就會影響計時的準確,所以頗有必要單獨開一個線程來計時
何時會用到定時器線程?當使用setTimeOut或setInterval時,他須要定時器線程計時,計時完成後就會將特定的事件推入事件隊列中。
譬如:
setTimeout(function(){
console.log('hello!');
},1000);
這段代碼的做用是1000毫秒計時完成後(由定時器線程計時),將回調函數推入事件隊列中,等待主線程執行
setTimeout(function(){
console.log('hello!');
},0);
console.log('begin');
這段代碼的效果是最快的時間內將回調函數推入事件隊列中,等待主線程執行
注意
執行結果是先begin後hello!
雖然代碼的本意是0毫秒後就推入事件隊列,可是W3C在HTML標準中規定,要求setTimeout中低於4ms的時間間隔算爲4ms
就算不等待4ms,就算假設0毫秒就推入事件隊列,也會先執行begin(由於只有可執行棧內空了後纔會主動讀取事件隊列)