單線程JavaScript為何如此高效
什么是js執行機制
JavaScript 的執行機制指的是 JavaScript 代碼在運行時的工作方式和順序。它涉及以下幾個關鍵概念:
- 單線程:JavaScript 是一門單線程的編程語言,意味著它只有一個主線程用于執行代碼。這意味著 JavaScript 中的代碼是按順序執行的,一次只能執行一個任務。
- 任務隊列:JavaScript 通過任務隊列來管理要執行的任務。任務隊列中存放著各種類型的任務,包括同步任務和異步任務。
- 事件循環:JavaScript 的事件循環是一個持續運行的過程,它負責監視任務隊列并選擇下一個要執行的任務。事件循環不斷地從任務隊列中獲取任務并將其交給主線程執行。
- 同步任務和異步任務:同步任務是按照順序在主線程上執行的任務,執行一個任務時會阻塞后續任務的執行。異步任務是在主線程上注冊并在將來某個時間點執行的任務,執行異步任務時不會阻塞后續任務的執行。
- 微任務和宏任務:JavaScript 中的任務可以分為微任務和宏任務。微任務是在當前任務執行完畢后立即執行的任務,它們使用微任務隊列進行管理。而宏任務是在事件循環的下一輪中執行的任務,它們使用任務隊列進行管理。
JavaScript 的執行順序:
- 執行同步任務,將函數調用和變量分配到調用棧中按順序執行。
- 遇到異步任務,如定時器、事件監聽等,將其注冊到任務隊列中,并繼續執行后續的同步代碼。
- 當同步代碼執行完畢后,主線程會檢查微任務隊列,依次執行隊列中的微任務。
- 執行完微任務后,主線程會從任務隊列中取出一個宏任務執行。
- 循環執行步驟 3 和步驟 4,直至任務隊列和微任務隊列都為空。
需要注意的是,JavaScript 中的異步任務通常是通過回調函數、Promise、async/await 等機制來處理。通過合理使用異步任務和任務隊列,可以實現非阻塞的代碼執行,提高代碼的性能和響應能力。JavaScript 的執行機制主要涉及以下幾個概念:調用棧、事件循環和任務隊列。文字有點單調,看看下面的圖理解理解
圖片
讓我們通過一個例子來解釋這些概念。假設我們有以下代碼:
console.log("Script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
Promise.resolve().then(function() {
console.log("Promise");
});
console.log("Script end");
這段代碼的執行機制如下:
- 首先,開始執行代碼,遇到第一行 console.log("Script start"),它會立即打印 "Script start"。
- 接下來,遇到 setTimeout,它是一個異步函數,會被放入任務隊列中,并設置一個定時器。由于定時器時間為 0,所以不會立即執行。
- 然后,遇到 Promise.resolve().then(),它會創建一個 Promise 對象,并將 .then() 中的回調函數放入微任務隊列中。
- 繼續執行下一行,打印 "Script end"。
- 此時,主線程上的同步代碼執行完畢,開始執行微任務隊列中的任務。首先執行 Promise 的回調函數,打印 "Promise"。
- 接著,主線程開始執行任務隊列中的任務。由于定時器時間到達,setTimeout 的回調函數被放入任務隊列中。
- 最后,主線程執行任務隊列中的任務,打印 "setTimeout"。
綜上所述,JavaScript 的執行機制遵循以下步驟:
- 執行同步代碼,將函數調用和變量分配到調用棧中按順序執行。
- 遇到異步操作,如定時器、事件監聽等,將其注冊到任務隊列中,并繼續執行后續的同步代碼。
- 同步代碼執行完畢后,主線程會檢查微任務隊列,依次執行隊列中的微任務(Promise 回調函數)。
- 執行完微任務后,主線程會從任務隊列中取出任務執行,執行完一個任務后再檢查微任務隊列,如此循環,直至任務隊列為空。
需要注意的是,微任務優先級高于任務隊列中的任務,所以在執行任務隊列中的任務之前,會先執行完所有的微任務。
現學現用,再看一個例子:
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("js start");
setTimeout(function () {
console.log("timeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise");
resolve();
}).then(function () {
console.log("then");
});
console.log("js end");
這段代碼的打印順序如下:
- "js start":立即打印,表示 JavaScript 代碼的開始執行。
- "async1 start":由于 async1 函數被調用,所以會打印 "async1 start"。
- "async2":async1 函數中調用了 async2 函數,因此會打印 "async2"。
- "promise":new Promise 的回調函數立即執行,所以會打印 "promise"。
- "js end":立即打印,表示 JavaScript 代碼的執行結束。
- "async1 end":由于 async2 函數是一個異步函數,await async2() 表達式會等待 async2 函數執行完畢,然后繼續執行下面的代碼,所以會打印 "async1 end"。
- "then":Promise 的 then 方法是異步執行的,所以會在下一個事件循環中執行,因此會打印 "then"。
- "timeout":由于 setTimeout 的延遲時間為 0,所以會在下一個事件循環中執行,因此會打印 "timeout"。代碼的執行順序是按照同步代碼的順序執行,異步代碼則根據事件循環的機制來執行。async/await 會暫停同步代碼的執行,并等待異步操作完成后再繼續執行后續的代碼。
總結
JS 代碼的執行順序主要為:
- 同步代碼 同步代碼(sync code)直接進入執行棧執行。執行順序按代碼書寫順序。
- 異步任務回調 異步任務(如 setTimeout)進入任務隊列。
- 事件循環 事件循環周期性地從任務隊列取出任務,推入執行棧執行。當執行棧為空時,才會取出隊列中的任務。
- 執行棧先進后出 執行棧采用先進后出的方式執行函數。在函數執行完畢后才會執行上層函數。這保證了函數的正確嵌套調用。
- 微任務優先級高于宏任務
- 宏任務(macrotask):出于任務隊列的任務。比如 setTimeout、setInterval。
- 微任務(microtask):比如 Promise .then、MutationObserver 。微任務的優先級高于宏任務。所以整個執行順序可以描述為:
- 同步代碼按順序進入執行棧執行
- 異步宏任務進入任務隊列
- 當執行棧清空時,執行微任務
- 接著執行宏任務
- 循環往復