瀏覽器中的Event Loop
我們都知道JavaScript是單執行緒的,也就是說同一時間只能幹一件事。這是因為JavaScript主要是用來操作DOM
的,如果變成多執行緒,瀏覽器就懵逼了,不知道該聽誰的了。但是雖然js是單執行緒,但是完全可以模擬多執行緒,靠的就是Event Loop
。
我們都知道js中的程式碼分 同步
和 非同步
,所謂的 非同步
其實就是不會阻塞我們的主執行緒,等待主執行緒的程式碼執行完畢才會執行。callback setTimeout setInterval Promise ...
這些都是都是我們耳熟能詳的 非同步
程式碼
如圖所示,js中的記憶體分為 堆記憶體(heap)
和 棧記憶體(stack)
, 堆記憶體
中存的是我們宣告的object
型別的資料,棧記憶體
中存的是 基本資料型別
以及 函式執行時
的執行空間。我們的 同步
程式碼就放在 執行棧
中,那非同步程式碼呢?瀏覽器會將 dom事件 ajax setTimeout
等非同步程式碼放到佇列中,等待執行棧
中的程式碼都執行完畢,才會執行佇列中的程式碼,是不是有點像釋出訂閱模式。
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
console.log(3);
複製程式碼
根據之前說的,setTimeout 會被放到佇列中,等待執行棧中的程式碼執行完畢才會執行,所以會輸出1, 3, 2
但是非同步
程式碼也是有區別的:
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
Promise.resolve().then(() => {
console.log(3)
})
複製程式碼
輸出的永遠是1, 3, 2
, 也就是說 promise
在 setTimeout
之前執行了。這是因為 非同步任務
分為 微任務(microtask)
和 巨集任務(task)
,執行的順序是 執行棧中的程式碼 => 微任務 => 巨集任務
。
執行棧
執行棧
中的程式碼永遠最先執行
微任務(microtask): promise MutationObserver...
- 當
執行棧
中的程式碼執行完畢,會在執行巨集任務佇列
之前先看看微任務佇列
中有沒有任務,如果有會先將微任務佇列
中的任務清空才會去執行巨集任務佇列
巨集任務(task): setTimeout setInterval setImmediate(IE專用) messageChannel...
- 等待
執行棧
和微任務佇列
都執行完畢才會執行,並且在執行完每一個巨集任務
之後,會去看看微任務佇列
有沒有新新增的任務,如果有,會先將微任務
佇列中的任務清空,才會繼續執行下一個巨集任務
setTimeout(() => {
console.log('timeout1')
Promise.resolve().then(() => {
console.log('promise1')
})
Promise.resolve().then(() => {
console.log('promise2')
})
}, 100)
setTimeout(() => {
console.log('timeout2')
Promise.resolve().then(() => {
console.log('promise3')
})
}, 200)
複製程式碼
- 先將兩個
setTimeout
塞到巨集任務佇列中 - 當第一個
setTimeout1
時間到了執行的時候,首先列印timeout1,然後在微任務佇列中塞入promise1
和promise2
- 當第一個
setTimeout1
執行完畢後,會去微任務佇列檢查是不是空的,他發現了有兩個promise
,會把兩個promise
按順序執行完再去執行下一個巨集任務 - 兩個
promise
執行完畢後會微任務佇列中沒有任務了,會去巨集任務中執行下一個任務setTimeout2
- 當
setTimeout2
執行的時候,先列印一個timeout2,然後又在微任務佇列中塞了一個promise2
- 當
setTimeout2
執行完畢後會去微任務佇列檢查,發現有一個promise3,會將promise3
執行 - 會依次列印
timeout1 promise1 promise2 timeout2 promise3
Node中的Event Loop
我們都知道Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行環境,也就是能夠讓js在服務端執行。但是Node中的Event Loop是用libuv模擬的,它將不同的任務分配給不同的執行緒,形成一個Event Loop,以非同步的方式將任務的執行結果返回給V8引擎。
- Node中的微任務:
process.nextTric promise setImmediate...
- Node中的巨集任務:
setTimeout setInterval...
- timers:執行setTimeout() 和 setInterval()中到期的callback。
- I/O callbacks:上一輪迴圈中有少數的I/Ocallback會被延遲到這一輪的這一階段執行
- idle, prepare:僅內部使用
- poll:最為重要的階段,執行I/O callback,檢查有沒有timers到期 timer在適當的條件下會阻塞在這個階段
- check:執行setImmediate的callback
- close callbacks:執行close事件的callback,例如socket.on("close",func)
Node中的Event Loop會在每次切換佇列的時候 清空微任務佇列,也就會會將當前佇列都執行完,在進入下一階段的時候檢查一下微任務中有沒有任務
setTimeout(() => {
console.log('timeout1')
Promise.resolve().then(() => {
console.log('promise1')
})
Promise.resolve().then(() => {
console.log('promise2')
})
}, 0)
setTimeout(() => {
console.log('timeout2')
Promise.resolve().then(() => {
console.log('promise3')
})
}, 0)
複製程式碼
- 先將兩個
setTimeout
塞到巨集任務佇列中 - 當第一個
setTimeout1
時間到了執行的時候,首先列印timeout1,然後在微任務佇列中塞入promise1
和promise2
- 當第一個
setTimeout1
執行完畢後,繼續執行下一個setTimeout2
- 當
setTimeout2
執行的時候,先列印一個timeout2,然後又在微任務佇列中塞了一個promise2
- 當前巨集任務佇列清空,進入下一階段,去檢查微任務佇列中有沒有任務
- 清空微任務佇列
- 在node環境中執行 會依次列印
timeout1 timeout2 promise1 promise2 promise3