介紹
首先,地球人都知道JS是單線程的,是以JS同時隻能執行一個任務,也就是隻有一個調用棧,先執行同步任務,再執行異步任務。
雖然HTML5允許JS腳本建立多個線程,但是子線程完全受主線程控制,且不得操作 DOM。是以,這個新标準并沒有改變JS單線程的本質。
什麼是異步任務
異步任務就是那些被引擎放在一邊,不進入主線程、而進入任務隊列的任務。
什麼是事件循環
事件循環(Event Loop)是 JavaScript 引擎處理異步任務的機制。它用來管理所有的任務隊列,包括宏任務和微任務隊列。當 JavaScript 引擎遇到異步任務時,會将其放入相應的任務隊列中,并繼續執行同步代碼,直到同步代碼執行完畢或遇到下一個異步任務。當目前的宏任務執行完畢後,JavaScript 引擎會按照一定的規則從微任務隊列中取出任務執行,直到微任務隊列為空;然後再從宏任務隊列中取出下一個宏任務執行。這個過程就是事件循環。
宏任務和微任務都有
宏任務有
事件的回調函數,新程式或子程式被直接執行\<script>,setTimeout()和setInterval() requestAnimationFrame, i/o操作,setImmediate, UI rendering
微任務有
promise.then() catch() finally() MutationObserver. Object.observe async/await node.js 中的process.nextTick() queueMicrotask()
事件循環怎麼算一輪呢?
事件循環(Event Loop)是一個持續運作的機制,它不斷地執行任務隊列中的任務。一輪事件循環通常包括以下幾個階段:
- 執行目前宏任務:從宏任務隊列中取出一個宏任務執行。
- 執行目前宏任務中産生的微任務:當一個宏任務執行完畢後,會立即處理所有已經排隊的微任務,按照它們被添加到微任務隊列的順序依次執行。
- 更新渲染:如果需要進行頁面渲染,會執行相應的渲染操作。
- 檢查是否有 Web Worker 任務:如果有,則執行 Web Worker 任務。
- 進入下一輪事件循環:檢查是否有新的宏任務需要執行。如果有,跳轉到第 1 步,否則繼續等待新的任務被添加到隊列中。 一輪事件循環的結束并不一定意味着整個程式的結束,它隻是按照上述流程完成了一次任務的執行。事件循環會持續不斷地運作,等待新的任務被添加到隊列中,并按照上述流程執行。
是以輸出順序為 同步任務->異步任務(微任務->宏任務)
或者 宏任務->微任務->宏任務
代碼例子
<script>
console.log("Start");
setTimeout(function () {
console.log("這是定時器");
}, 0);
new Promise(() => {
console.log("這是Promise構造函數");
resolve();
}).then(() => {
console.log("這是Promise.Then");
});
console.log("End");
</script>
事件循環流程
- 整體script作為第一個宏任務進入主線程,遇到console.log(Start),輸出Start
- 遇到setTimeout,其回調函數被分發到宏任務中
- 遇到newPromise,new Promise構造函數執行,輸出"這是Promise構造函數"
- 遇到then被分發到微任務中
- 遇到console.log("End"),輸出End
- 調用棧被清空以後 事件循環就會優先尋找微任務隊列裡面的任務
- 我們發現了then在微任務裡面,執行輸出“這是Promise.then”
- 第一輪事件循環結束,開始第二輪事件循環
- 宏任務有setTimeout對應的回調函數,立即執行輸出“這是定時器”
輸出結果
- Start
- 這是Promise構造函數
- End
- 這是Promise.Then
- 這是定時器
這次來個複雜的例子 宏任務嵌套微任務 微任務嵌套宏任務 這次把script這個大宏任務忽略 以同步任務角度開始看
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout1");
});
setTimeout(function () {
console.log("setTimeout2");
Promise.resolve().then(() => {
console.log("then1");
});
Promise.resolve().then(() => {
console.log("then2");
});
});
async1();
new Promise((res) => {
console.log("this is Promise");
res();
}).then(() => {
console.log("then3");
setTimeout(() => {
console.log("then3 setTimeout3");
});
});
console.log("script end");
- 遇到函數async1 async2沒有執行跳過
- 遇到console.log('script start'),輸出script start
- 遇到setTimeout1其回調函數分發到宏任務中
- 遇到setTimeout2其回調函數分發到宏任務中
- 遇到async1()函數執行, 遇到console.log("async1 start"),輸出async start
- 在async1函數中遇到await async2() async2()優先級高于await運算符 async2()函數執行
- 在async2函數中遇到console.log("async2")輸出 async2
- 回到async1函數中 ,由于async函數使用await後的語句會被放入一個回調函數中,是以await後續代碼分發到微任務中
- 遇到new Promise構造函數中 console.log("this is Promise"),直接執行 輸出this is Promise
- then方法被分發到微任務中
- 遇到console.log("script end")
- 同步任務執行完了 開始執行異步任務 根據eventloop先執行任務隊列中的微任務
- 任務隊列先入先出 是以先輸出'async1 end' 後輸出 new Promise.then中的内容 then3,
- new Promise.then()中遇到setTimeout放到宏任務隊列中,
- 事件循環第一輪結束,開始第二輪
- 宏任務隊列setTimeout1拿出來輸出
- 事件循環第二輪結束,開始第三輪
- setTimeout2 執行 輸出setTimeout1和setTimeout2
- setTimeout2中有兩個.then方法分發到微任務 再執行輸出 then1和then2
- 事件循環第三輪結束,開始第四輪
- 最後一個宏任務 輸出 then3 setTimeout3
是以代碼輸出結果
- script start
- async1 start
- async2
- this is Promise
- script end
- async1 end
- then3
- setTimeout1
- setTimeout2
- then1
- then2
- then3 setTimeout3
需要注意的是,微任務的執行順序是按照它們被添加到微任務隊列的順序來執行的。即使某個微任務的産生時間晚于其他微任務,但如果它被添加到隊列較早,那麼它仍然會先于其他微任務執行。
文章到這裡就結束了,希望對你有所幫助
作者:ZhaiMou
連結:https://juejin.cn/post/7330300019022970915