本文要講的是,瀏覽器讀一個script代碼的流程是什麼,遇到異步代碼會如何處理,宏觀任務和微觀任務如何處理。javascript
開始前先來看幾個概念。html
首先要說一個棧模型,函數的調用造成了棧幀。java
function f1() {
f2();
}
function f2() {}
f1();
複製代碼
例如這段代碼,調用 f1
時,建立第一幀;f1
調用 f2
時,建立第二幀。第二幀壓在第一幀之上,當 f2
運行完成,此時最上層第二幀彈出棧,當 f1
運行完成,此時最上層第一幀也彈出棧,棧就空了。也就是常說的後進先出。web
這個棧也就是常說的 執行棧,執行的是任務隊列裏的任務。api
而後說一下隊列。隊列中放着任務,也就是函數。promise
若是有新的任務(例如用戶觸發了點擊事件),會加入隊列,排在後面。瀏覽器
隊列裏的任務會放在執行棧中執行。app
隊列有兩種:宏觀隊列和微觀隊列。
分別放着兩種任務:宏觀任務和微觀任務。webapp
宿主發起的任務異步
如 包含代碼的script, setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
JavaScript引擎發起的任務爲微觀任務
如 process.nextTick, Promises, Object.observe, MutationObserver
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲Event Loop(事件循環)。
執行棧(stack)執行過程當中若是遇到異步代碼,會移除本次執行,等處理完成後加入對應的任務列表。例如setTime延遲結束後會加入宏觀隊列,promise執行完成後會加入微觀隊列。至因而在event loop開始前判斷是否加入隊列,仍是另外開了一個線程去執行完成後加入主線程隊列,這裏不作研究。
事件循環,宏任務,微任務的關係如圖所示:
script標籤的整塊代碼是執行棧的第一個執行任務。
下面來看一段代碼
setTimeout(() => console.log(1))
console.log(2);
new Promise((resolve, reject) => {
console.log(3)
resolve();
}).then(() => console.log(4))
console.log(5)
複製代碼
執行過程:
因此執行結果:2 3 5 4 1
var t = +new Date();
setTimeout(() => {
console.log('timer 2秒,實際時間爲3秒');
}, 2000)
setTimeout(() => {
console.log('timer 1秒,實際時間爲3秒');
}, 1000)
while (+new Date() - t < 3000) {} // 延遲3秒
複製代碼
解析:第一秒時候,第二個setTimeout插入宏觀任務隊列;第二秒時,第一個setTimeout插入宏觀任務隊列;第三秒
可見setTimeout延遲結束後當即插入宏觀任務隊列。
因此執行結果爲:
timer 1秒,實際時間爲3秒
timer 2秒,實際時間爲3秒
setTimeout(() => console.log(1))
fetch('https://deployment.whosmeya.com/api/getok')
.then(() => console.log(2))
var t = +new Date();
while (+new Date() - t < 2000) {} // 延遲兩秒
複製代碼
解析:雖然宏觀任務setTimeout在微觀任務promise以前完成,第一次宏任務(整塊代碼)執行結束後,監測到有可執行微觀任務,因此先執行微觀任務。
因此執行結果:2 1
setTimeout(() => console.log(1))
new Promise(function (resolve, reject) {
setTimeout(() => resolve())
}).then(() => console.log(2))
複製代碼
解析:第一個宏任務(代碼塊)執行完成後,宏觀任務隊列會有兩個宏觀任務,因此第一個setTime先執行。 執行棧的任務執行順序爲:代碼塊,第一個setTimeout,第二個setTimeout,promise.then。
因此執行結果爲:1 2
event loop循環步驟:
開始循環時,javascript標籤裏的總體代碼是第一個任務。
參考