js的單執行緒和瀏覽器的多執行緒
js執行是單執行緒:(傳送請求,接受請求,渲染頁面,執行js等等這些就是一個個執行緒。)
JS引擎
通常講到瀏覽器的時候,我們會說到兩個引擎:渲染引擎和JS引擎。渲染引擎就是如何渲染頁面,Chrome/Safari/Opera用的是Webkit引擎,IE用的是Trident引擎,FireFox用的是Gecko引擎。不同的引擎對同一個樣式的實現不一致,就導致了經常被人詬病的瀏覽器樣式相容性問題。這裡我們不做具體討論。
JS引擎可以說是JS虛擬機器,負責JS程式碼的解析和執行。通常包括以下幾個步驟:
- 詞法分析:將原始碼分解為有意義的分詞
- 語法分析:用語法分析器將分詞解析成語法樹
- 程式碼生成:生成機器能執行的程式碼
- 程式碼執行
不同瀏覽器的JS引擎也各不相同,Chrome用的是V8,FireFox用的是SpiderMonkey,Safari用的是JavaScriptCore,IE用的是Chakra。
之所以說JavaScript是單執行緒,就是因為瀏覽器在執行時只開啟了一個JS引擎執行緒來解析和執行JS。那為什麼只有一個引擎呢?如果同時有兩個執行緒去操作DOM,瀏覽器是不是又要不知所措了。
所以,雖然JavaScript是單執行緒的,可是瀏覽器內部不是單執行緒的。一些I/O操作、定時器的計時和事件監聽(click, keydown...)等都是由瀏覽器提供的其他執行緒來完成的。
一個瀏覽器通常由以下幾個常駐的執行緒:
- 渲染引擎執行緒:顧名思義,該執行緒負責頁面的渲染
- JS引擎執行緒:負責JS的解析和執行
- 定時觸發器執行緒:處理定時事件,比如setTimeout, setInterval
- 事件觸發執行緒:處理DOM事件
- 非同步http請求執行緒:處理http請求
需要注意的是,渲染執行緒和JS引擎執行緒是不能同時進行的。渲染執行緒在執行任務的時候,JS引擎執行緒會被掛起。因為JS可以操作DOM,若在渲染中JS處理了DOM,瀏覽器可能就不知所措了。
同步執行和非同步執行
- 同步:只有前一個任務執行完畢,才能執行後一個任務
- 非同步:當同步任務執行到某個
WebAPI
時,就會觸發非同步操作,此時瀏覽器會單獨開執行緒去處理這些非同步任務。
任務佇列、回撥佇列、事件迴圈
WebAPI
是啥?瀏覽器事件、定時器、ajax
,這些操作不會阻塞 JS 的執行,JS 會跳過當前程式碼,執行後續程式碼
- 任務佇列( Task Queue ):主執行緒執行完畢後所觸發的非同步任務( WebAPIs ),叫任務佇列
- 回撥佇列( Callback Queue ):這些非同步 WebAPI 執行完成後得到的結果,會新增到 callback queue 中
- 事件迴圈( Event Loop ):只要主執行緒的同步任務執行完畢,就會不斷的讀取 "回撥佇列" 中的回撥函式,到主執行緒中執行,這個過程不斷迴圈往復
如何知道主執行緒執行執行完畢?JS引擎存在 monitoring process 程序,會持續不斷的檢查主執行緒執行為空,一旦為空,就會去 callback queue 中檢查是否有等待被呼叫的函式。
console.log('1');
setTimeout(function() {
console.log('2');
}, 0);
console.log('3');
- 列印1
- 遇到 WebAPI(
setTimeout
) ,瀏覽器新開定時器執行緒處理,執行完成後把回撥函式存放到回撥佇列中。專業一點的說發:JS
引擎遇到非同步任務後不會一直等待其返回結果,而是將這個任務掛起交給其他瀏覽器執行緒處理,自己繼續執行主執行緒中的其他任務。這個非同步任務執行完畢後,把結果返回給回撥佇列。被放入的程式碼不會被立即執行。而是當主執行緒所有同步任務執行完畢, monitoring process 程序就會把 "回撥佇列" 中的第一個回撥程式碼放入主執行緒。然後主執行緒執行程式碼。如此反覆 - 列印3 非同步
setTimeout
不會阻塞同步程式碼,因此會首先列印3 - 主執行緒執行完畢後,執行 Callback Queue 列印2
非同步任務的執行優先順序並不相同,它們被分為兩類:微任務( micro task ) 和 巨集任務( macro task ) 根據非同步事件的型別,這些事件實際上會被派發對應的巨集任務和微任務中,在當前主執行緒執行完畢後,
- 會先檢視微任務中是否有事件存在,如果不存在,則再去找巨集任務
- 如果存在,則會依次執行佇列中的引數,直到微任務列表為空,讓後去巨集任務中一次讀取事件到主執行緒中執行,如此反覆 當前主執行緒執行完畢後,會首先處理微任務佇列中的事件,讓後再去讀取巨集任務佇列的事件。在同一次事件迴圈中,微任務永遠在巨集任務之前執行。
- 巨集任務( macro-task ):整體
script
、setTimeout
、setInterval
、UI互動事件
、I/O
- 微任務( micro-task ):
process.nextTick
、Promise
、MutaionObserver
(function test() {
setTimeout(function() {console.log(4)}, 0);
new Promise(function (resolve, reject) {
console.log(1);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(2);
}).then(function() {
console.log(5);
});
console.log(3);
})()
1. setTimeout:巨集任務:存入巨集任務佇列
2. Promise:函式本身是同步執行的( **Promise** 只有一個引數,預設new的時候就會同步執行), `.then` 是非同步,因此依次列印1、2 `.then` 存入微任務中
3. 列印3( 第一次主執行緒執行完畢 )
4. 執行微任務中的回撥函式:5, 讓後執行巨集任務中的 `setTimeout` 4
// 最終結果1,2,3,5,4