JavaScript運作機制
前言
本文要講的是,浏覽器讀一個script代碼的流程是什麼,遇到異步代碼會如何處理,宏觀任務和微觀任務如何處理。
開始前先來看幾個概念。
棧(後進先出)
首先要說一個棧模型,函數的調用形成了棧幀。
function f1() {
f2();
}
function f2() {}
f1();
例如這段代碼,調用 f1 時,建立第一幀;f1 調用 f2 時,建立第二幀。第二幀壓在第一幀之上,當 f2 運作完成,此時最上層第二幀彈出棧,當 f1 運作完成,此時最上層第一幀也彈出棧,棧就空了。也就是常說的後進先出。
這個棧也就是常說的 執行棧,執行的是任務隊列裡的任務。
隊列(先進先出)
然後說一下隊列。隊列中放着任務。
如果有新的任務(例如使用者觸發了點選事件),會加入隊列,排在後面。
隊列裡的任務會放在執行棧中執行。
隊列有兩種:宏觀任務隊列和微觀任務隊列。
分别放着兩種任務:宏觀任務和微觀任務。
宏觀任務(Task,或者叫MacroTask)
宿主發起的任務。
如 包含代碼的script, setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
微觀任務(MicroTask)
JavaScript引擎發起的任務為微觀任務。微任務相當于加急的任務,時間循環會優先處理完微任務。
如 process.nextTick, Promises, Object.observe, MutationObserver
浏覽器中的事件循環(Event Loop)
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,是以整個的這種運作機制又稱為Event Loop(事件循環)。
執行棧(stack)執行過程中如果遇到異步代碼,會将其移除本次執行,加入到其他線程中,處理完成後該線程會把任務添加到對應的任務隊列隊尾。例如setTime延遲結束後會被添加到宏觀任務隊列,promise執行完成後會被添加到微觀任務隊列。
事件循環,宏任務,微任務的關系如圖所示:
script标簽的整塊代碼是執行棧的第一個執行任務。
下面來看一段代碼
測試:宏任務微任務執行順序
setTimeout(() => console.log(1))
console.log(2);
new Promise((resolve, reject) => {
console.log(3)
resolve();
}).then(() => console.log(4))
console.log(5)
執行過程:
執行第一個宏任務(整塊代碼)
遇到setTimeout,跳過(延遲結束後被添加到宏觀任務隊列)
列印 2
new promise屬于同步代碼,列印 3,then屬于異步,跳過promise.then(完成後被添加到微觀任務隊列)
列印 5
執行結束
有可執行的微任務,開始微任務
列印4
沒有可執行的微任務,開始宏任務
列印1
沒有微任務,沒有宏任務。
是以執行結果:2 3 5 4 1
測試:setTimeout第二個參數是最小延遲時間
var t = +new Date();
setTimeout(() => {
console.log('timer 2秒,實際時間為3秒');
}, 2000)
console.log('timer 1秒,實際時間為3秒');
}, 1000)
while (+new Date() - t < 3000) {} // 延遲3秒
解析:第一秒時候,第二個setTimeout插入宏觀任務隊列;第二秒時,第一個setTimeout插入宏觀任務隊列;第三秒
第一秒:第二個setTimeout插入宏觀任務隊列;
第二秒:第一個setTimeout插入宏觀任務隊列;
第三秒:代碼執行結束;
沒有可執行微觀任務,執行宏觀任務 輸出 'timer 1秒,實際時間為3秒',代碼執行結束;
沒有可執行微觀任務,執行宏觀任務 輸出 'timer 2秒,實際時間為3秒',代碼執行結束;
可見setTimeout延遲結束後立即插入宏觀任務隊列。
是以執行結果為:
timer 1秒,實際時間為3秒
timer 2秒,實際時間為3秒
測試:宏任務在微任務之前完成
fetch('
https://deployment.whosmeya.com/api/getok')
.then(() => console.log(2))
while (+new Date() - t < 2000) {} // 延遲兩秒
解析:雖然宏觀任務setTimeout在微觀任務promise之前完成,第一次宏任務(整塊代碼)執行結束後,監測到有可執行微觀任務,是以先執行微觀任務。
是以執行結果:2 1
測試:宏任務包含微任務
new Promise(function (resolve, reject) {
setTimeout(() => resolve())
}).then(() => console.log(2))
解析:第一個宏任務(代碼塊)執行完成後,宏觀任務隊列會有兩個宏觀任務,是以第一個setTime先執行。 執行棧的任務執行順序為:代碼塊,第一個setTimeout,第二個setTimeout,promise.then。
是以執行結果為:1 2
總結
javascript是單線程的意思是 主線程為單線程,除此之外還擁有渲染線程,事件線程等。
event loop 總是不停的監聽任務隊列,有任務就放進執行棧。
微觀任務就像加急任務,總是優先全部執行。
執行棧遇到異步代碼會給其他線程執行,執行完成後會被該線程插入對應任務隊列。
參考
這一次,徹底弄懂 JavaScript 執行機制
Event Loop的規範和實作
JavaScript 運作機制詳解:再談Event Loop
Concurrency model and the event loop
whatwg event loops
whatwg timers
原文位址
https://www.cnblogs.com/whosmeya/p/12557622.html