天天看點

JavaScript運作機制

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

繼續閱讀