為什麼JavaScript是單線程? JavaScript的一大特點就是單線程, 同一時間只能做一件事情,主要和它的用途有關, JavaScript主要是控制和用戶的交互以及操作DOM。註定它是單線程。 假如是多個線程, 一個移除DOM節點,一個新增DOM節點,瀏覽器以誰的為準呢? 什麼是執執行棧呢 ...
為什麼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)的代碼(同步任務),總是在讀取"任務隊列"(非同步任務)之前執行。