JavaScript Event Loop和微任務、宏任務
為什麽JavaScript是單線程?
JavaScript的一大特點就是單線程, 同一時間只能做一件事情,主要和它的用途有關, JavaScript主要是控制和用戶的交互以及操作DOM。註定它是單線程。 假如是多個線程, 一個移除DOM節點,一個新增DOM節點,瀏覽器以誰的為準呢?
什麽是執執行棧呢?
函數的調用就會形成一個棧幀。當執行棧都為空的時候,主線程就會處於空閑狀態。
function fn2(x, y) { return x + y } function fn1(z) { let a = 10 return fn2(a, z) } console.log(fn1(5)) // 15
以上代碼: fn1
函數調用時會創建一個執行棧,棧中包含fn1
的參數和局部變量。當 fn1
調用 fn2
時, 第二個執行棧就會被創建, 並且壓入到第一個執行棧之前。 棧中包含了 fn2
的參數和全局變量。當 fn2
執行完返回時,最前面的執行棧就會被彈出。剩下 fn1
函數的調用幀, 當fn1
函數執行完並返回時, 執行棧就空了。
任務隊列
任務隊列主要用戶掛起等待中的任務(異步任務)。
JavaScript是單線程, 意味著所有的任務需要排隊, 前一個任務執行完,才能進行下一個任務。 AJAX就是典型的異步任務,需要調用HTTP線程,然後發送request請求,再是等待服務端的響應。在結果沒有返回執行,後面的代碼是不會執行的,這會給用戶一種網站卡的現象。
因此, JavaScript 分為 同步任務
在主線程上排隊執行的任務,也就是前面執行完畢,才能執行下一個的任務。 異步任務
是指它不會進行主線程,不會影響後續代碼的執行,而是進入任務隊列
,當異步任務執行有了結果,就會在任務隊列中放置一個事件,等主線程空閑(執行棧為空,同步任務執行完畢
),通知任務隊列進入主線程執行。
微任務和宏任務
任務隊列中的異步任務分為 微任務
和 宏任務
常見的微任務有: process.nextTick
、Promise
和 MutationObserver
監聽DOM的變化。
常見的宏任務: setTimeout
、setInterval
、setImmediate
、 script
I/O操作
、 UI渲染
等。
微任務和宏任務的區別:
- 微任務進入主線程執行是一隊一隊的, 而宏任務進入主線程是一個一個的。
- 微任務是在主線程空閑時批量執行, 宏任務是在事件循環下一輪的最開始執行
例子: 以下代碼的打印結果
console.log(1)
setTimeout(function() {
console.log(2)
})
Promise.resolve()
.then(function() {
console.log(3)
})
console.log(4)
// 打印結果: 1 4 3 2
整個的執行過程:
stack(執行棧)、Micro(微任務)、Macro(宏任務)
1.初始狀態: stack:[], Micro: [], Macro: [script]。執行棧為空, 微任務為空, 宏任務隊列中有一個整體的 script代碼
2. 主線程開始執行, 遇到console.log(1), 首先會打印 1
3. 繼續向下執行,遇到 setTimeout異步任務,就將其加入到Macro(宏任務)隊列中。等待執行
4. 繼續向下執行, 遇到 Promise.resolve也是一個異步任務,單它是微任務,將其加入 Micro(微任務)隊列中,等待著行
5. 解析console.log(4), 並且打印4。 當主線程執行完打印的結果依次是 1 和 4。
6. 這時候主線程就會問 任務(異步)隊列,有沒有微任務要執行,將所有的 Micro(微任務)加入執行棧執行, 打印結果 3
7. 微任務執行完了, 就開始下一輪事件循環, 將第一個 Macro(宏任務)壓入執行棧執行, 再次打印 2。
Event Loop事件循環
只要主線程一空閑就會將 "任務隊列中的異步任務"依次壓入執行棧, 這個過程是循環不斷的,所以整個運行機制稱之為 Event Loop(事件循環)。
執行棧中(stack)的代碼(同步任務),總是在讀取"任務隊列"(異步任務)之前執行。
圖示
參考文章
- 並發模型與事件循環 MDN
- JavaScript 運行機制詳解:再談Event Loop
- 從一道題淺說 JavaScript 的事件循環
JavaScript Event Loop和微任務、宏任務