談談Event Loop
無論是瀏覽器的Event loop 還是 Nodejs 中的Event loop,都是基於JS 的單執行緒設計而生的(純屬個人理解),通過事件迴圈實現非阻塞非同步執行效果。
幾個概念
談到事件機制首先弄清楚主執行緒
,執行棧
, 任務佇列
這幾個概念。
主執行緒:執行JS 程式碼。JS 的單執行緒針對的是單一的主執行緒,不是隻能有一個執行緒。
執行棧:關於執行棧的概念結合執行上下文來看,《JavaScript深入之執行上下文棧》
任務佇列Task Queue:即佇列,是一種先進先出的資料結構
三者的關係是:主執行緒要執行的都在執行棧裡,執行棧裡的內容是從任務佇列裡拿過來的。
關於任務
任務可以劃分為巨集任務
和微任務
。
巨集任務MacroTask
: 整個script , setTimeout, setInterval, setImmidiate(瀏覽器暫時不支援,只有IE10,但是在nodejs 中常用到)、I/O、UI Redering。
微任務(MicroTask)
:Promise, Async/Await(底層就是Promise)、Process.nextTick (node獨有)、MutationObserver。
具體執行時,任務還有另一種的劃分就是同步任務
和非同步任務
對於同步任務來說 ,放入執行棧裡按序依次執行的,但是對於非同步任務而言,是在非同步任務有了結果之後將非同步任務的回撥函式放到任務佇列裡等待主執行緒空閒時執行。注意這裡是在執行棧中如果遇到非同步任務,是註冊非同步任務的回撥函式 ,把回撥函式放到任務佇列中(
這一步會產出任務
)。
瀏覽器中的Event Loop
事件迴圈的程式模型
- 執行棧一開始認為是空,將巨集任務: 整體的script 壓入棧執行。
- 執行過程中遇到同步任務按序一步一步執行,遇到非同步任務註冊非同步任務的回撥函式放到對應的任務佇列中(這裡有巨集任務佇列,微任務佇列)。這樣就產生了新的macro-task 和 micro-task。
- script 中程式碼執行完,將script 出棧,也是一次巨集任務出棧 。
- 檢測任務佇列中是否有微任務,清理微任務佇列。這裡需要注意的是巨集任務出隊是一個一個出,而微任務是一隊一隊出。
- 更新頁面渲染
- 檢測是否有web worker 任務,處理web worker。
- 上述過程迴圈往復,直到兩個佇列都清空。
總結一下整體的流程:
主執行緒首先會執行一個巨集任務,當此巨集任務執行完之後,會去檢視是否有微任務佇列。如果有,那就清空整個微任務佇列。如果沒有,會去檢視巨集任務佇列,取巨集任務佇列中的第一個去執行,執行巨集任務的過程中遇到微任務,依次加入微任務佇列等待下次執行。(
當然執行微任務的時候也會產生巨集任務,主執行緒會放入巨集任務佇列
)。棧空後,再次讀取任務佇列中的任務,以此類推。
舉個例子
console.log('script start')
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => {
console.log('promise1')
})
}, 0)
new Promise(resolve => {
console.log('promise2');
setTimeout(() => {
console.log('setTimeout2');
resolve();
}, 0)
})
Promise.resolve().then(() => {
console.log('promise3');
setTimeout(() => {
console.log('setTimeout 3')
},0)
}).then(() => console.log('promise4'))
console.log('script end');
複製程式碼
輸出結果:
script start
promise2
script end
promise3
promise4
setTimeout1
promise1
setTimeout2
setTimeout3
複製程式碼
詳細執行流程如下:
1. 初始巨集任務佇列只有整體script,執行棧、微任務佇列皆為空
巨集任務script入棧執行:
輸出
script start
遇見
setTimeout1
放入巨集任務佇列執行同步程式碼輸出
promise2
,遇見setTimeout2
放入巨集任務佇列遇見微任務
promise3
、promise4
放入微任務佇列輸出
script end
我們看下第一個巨集任務執行完的狀態:
巨集任務佇列: setTimeout1
、setTimeout2
微任務佇列: promise3
、 promise4
2. 接下來去清理微任務佇列
微任務佇列遵循先進先出原則,
promise3
入棧執行:輸出promise3、
,將setTimeout3
放入巨集任務佇列。promise3
出棧 ,promise4
入棧執行,輸出promise4
清理完微任務後的狀態:
巨集任務佇列:setTimeout1
、setTimeout2
、setTimeout3
微任務佇列:空
3. 從巨集任務佇列中取出第一個入棧執行
setTimeout1
入棧執行,輸出setTimeout1
,將promise1
放入微任務佇列
本次執行完的狀態:
巨集任務佇列: setTimeout2
、setTimeout3
微任務佇列: promise1
4. 清理微任務佇列
輸出
promise1
本輪執行完狀態:
巨集任務佇列: setTimeout2
、setTimeout3
微任務佇列: 空
5、 從巨集任務佇列中取出第一個執行
輸出
setTimeout2
本輪執行完狀態:
巨集任務佇列: setTimeout3
微任務佇列: 空
6. 微任務佇列為空,繼續從巨集任務佇列取出第一個執行
輸出
setTimeout3
至此巨集任務佇列
和微任務佇列
都為空。
從上述執行過程中, 不難看出,瀏覽器的Event Loop 總是一個巨集任務 ——> 一隊微任務 這種順序依次迴圈
另一個例子
var a = async function () {
await Promise.resolve().then(() => console.log(111));
console.log(222)
}
a().then(() => console.log(333))
var b = async function () {
await setTimeout(() => console.log('aaa'), 0);
console.log('bbb')
}
b().then(() => console.log('ccc'))
var c = async function () {
await console.log('A');
console.log('B')
}
c().then(() => console.log('C'))
複製程式碼
輸出結果:
A
->111
->bbb
->B
->222
->ccc
->C
->333
->aaa
執行順序:
1. script入棧,
執行同步程式碼,呼叫a():將111放入微任務佇列。繼續往下走執行b(),將aaa 放入巨集任務佇列,將 bbb 放入微任務佇列,接著執行c(), 列印
A
, 將B
放入微任務佇列。
此次執行完的狀態:
巨集任務佇列:
aaa
微任務佇列:
111
、bbb
、B
2. 清理微任務佇列:
執行 111, 列印
111
、將222
放入微任務佇列,執行bbb
:列印bbb
並將ccc
放入微任務佇列,執行B
: 列印B
並將C
放入微任務佇列,此時微任務佇列並沒有清理完,所以接著列印222
、ccc
、C
,列印完222
將333
放置微任務佇列,所以接著列印333
。
此次執行完的狀態:
巨集任務佇列:
aaa
微任務佇列: 空
- 執行aaa, 列印出
aaa
上述程式碼若是不好理解可以轉化為promise的方式來理解,效果如下:
new Promise(resolve => {
Promise.resolve()
.then(() => console.log(111))
.then(() => {
console.log(222)
resolve()
})
})
.then(() => console.log(333))
new Promise(resolve => {
setTimeout(() => console.log('aaa'), 0)
Promise.resolve().then(() => {
console.log('bbb')
resolve()
})
})
.then(() => console.log('ccc'))
new Promise(resolve => {
console.log('A')
Promise.resolve()
.then(() => {
console.log('B')
resolve()
})
})
.then(() => console.log('C'))
複製程式碼
但這純屬個人理解,若有問題,還請大神指導。
Nodejs中的Event Loop
1.簡介
Node 中的 Event Loop和瀏覽器中的是完全不相同的東西。Node.js採用V8作為js的解析引擎,而I/O處理方面使用了自己設計的libuv
,libuv
是一個跨平臺、專門寫給nodejs的庫,使用非同步,事件驅動的程式設計方式,核心是提供I/O的事件迴圈和非同步回撥。封裝了不同作業系統一些底層特性,對外提供統一的API。
2.六個階段
未完待續...