0. 引言:
最近寫了一些異步遞歸的代碼,着實有點頭疼,索性重新研究一下JavaScript 代碼執行順序,並附上一道面試題的解析。
1. JavaScript 代碼執行順序
首先我們了解幾個概念
1.1 微任務/宏任務
異步隊列中包括:微任務(micro-task) 和 宏任務(macro-task)
微任務包括: process.nextTick
,Promise
( process.nextTick
為 Node 獨有)
宏任務包括: script
, setTimeout
,setInterval
,setImmediate
,I/O
,UI rendering
Tips:
- 微任務優先級高於宏任務的前提是:同步代碼已經執行完成。因為
script
屬於宏任務,程序開始后會首先執行同步腳本,也就是script
。 Promise
里邊的代碼屬於同步代碼,.then()
中執行的代碼才屬於異步代碼。
1.2 Event Loop(事件輪詢)
Event Loop 是一個程序結構,用於等待和發送消息和事件。
Event Loop 執行順序如下所示:
- 首先執行同步代碼(宏任務)
- 當執行完所有同步代碼后,執行棧為空,查詢是否有異步代碼需要執行
- 執行所有微任務
- 當執行完所有微任務后,如有必要會渲染頁面
- 然后開始下一輪 Event Loop,執行宏任務中的異步代碼,也就是
setTimeout
中的回調函數
Tips:簡化講:先執行一個宏任務(script同步代碼),然后執行並清空微任務,再執行一個宏任務,然后執行並清空微任務,再執行一個宏任務,再然后執行並清空微任務......如此循環往復(一個宏任務 -> 清空微任務 -> 一個宏任務 -> 清空微任務)
2. 面試題詳解
2.1 題目
setTimeout(function () {
console.log(" set1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2 ");
});
});
new Promise(function (resolve) {
console.log("pr1");
resolve();
}).then(function () {
console.log("then1");
});
setTimeout(function () {
console.log("set2");
});
console.log(2);
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
2.2 執行過程解析
執行所有同步代碼(第一次宏任務):
setTimeout(function () { // setTimeout 內 function 放入宏任務
console.log(" set1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2 ");
});
});
new Promise(function (resolve) {
console.log("pr1"); // Promise里邊的代碼直接執行 打印 pr1
resolve();
}).then(function () {
console.log("then1"); // Promise.then 放入微任務
});
setTimeout(function () {
console.log("set2"); // setTimeout內function 放入宏任務
});
console.log(2); // 打印 2
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3"); //Promise.then 放入微任務
});
// 此時控制台打印 : pr1 > 2
// 異步任務隊列:[微任務數:2][宏任務數:2]
// 執行並清空微任務
執行並清空微任務
function () {
console.log("then1"); // 輸出 then1
}
function () {
console.log("then3"); // 輸出 then3
}
// 此時控制台打印 : then1 > then3
// 異步任務:[微任務數:0][宏任務數:2]
// 執行一個宏任務
執行一個宏任務
function () {
console.log(" set1"); //打印 set1
new Promise(function (resolve) {
resolve();
}).then(function () { // Promise.then 放入微任務
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2 ");
});
}
// 此時控制台打印 : set1
// 異步任務:[微任務數:1][宏任務數:1]
// 執行並清空微任務
執行並清空微任務
function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4"); // Promise.then 放入微任務
});
console.log("then2 "); // 打印 then2
}
// 此時控制台打印 : then2
// 異步任務:[微任務數:1][宏任務數:1]
// 此時微任務列表增加並未清空,繼續執行微任務
此時微任務列表增加並未清空,繼續執行微任務
function () {
console.log("then4"); // 打印 then4
}
// 此時控制台打印 : then4
// 異步任務:[微任務數:0][宏任務數:1]
// 執行宏任務
執行宏任務
function () {
console.log("set2"); // 打印 set2
}
// 此時控制台打印 : set2
// 異步任務:[微任務數:0][宏任務數:0]
// 程序結束
完整輸入順序
pr1
2
then1
then3
set1
then2
then4
set2