前置知識點(重要):
1.什麼是事件循環:js是單線程語言,同個時間執行一件事(同步),但是他可以有一個異步隊列,遇到異步操作(比如說ajax這種阻塞時間很久的事情)把它們先放入異步隊列,并且繼續往下執行,當同步隊列執行完了,他就會去異步隊列裡面找剛才存放起來的事件,然後按順序執行他們。
2.異步隊列(異步任務)又包含宏任務和微任務,微任務先與宏任務執行
宏任務有:
# | 浏覽器 | Node |
---|---|---|
setTimeout | √ | √ |
setInterval | √ | √ |
setImmediate | x | √ |
requestAnimationFrame | √ | x |
微任務有:
# | 浏覽器 | Node |
---|---|---|
process.nextTick | x | √ |
MutationObserver | √ | x |
Promise.then catch finally | √ | √ |
3.同為微任務的Promise與process.nextTick(callback),先執行後者(process.nextTick(callback)當下一輪事件開始循環的時候第一時間執行他的callback)
下圖為他們之間的關系圖:
通過一個例子來解釋執行整個事件循環,與同步異步,宏任務微任務執行順序
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
//
1.第一輪事件循環流程分析如下:(第一輪描述詳細一點,接下去都差不多)
– 進入主線程,遇到console.log,輸出1。
遇到setTimeout,其回調函數被分發到宏任務Event Queue中。我們暫且記為setTimeout1。
– 遇到process.nextTick(),其回調函數被分發到微任務Event Queue中。我們記為process1。
– 遇到Promise,new Promise(是同步,then才是異步)直接執行,輸出7。then(Promise中then是異步)被分發到微任務Event Queue中。我們記為then1。
– 又遇到了setTimeout,其回調函數被分發到宏任務Event Queue中,我們記為setTimeout2。
宏任務Event Queue | 微任務Event Queue |
---|---|
setTimeout1 | process1 |
setTimeout2 | then1 |
上表是第一輪事件循環宏任務結束時各Event Queue的情況,此時已經輸出了1和7。
我們發現了process1和then1兩個微任務。
執行process1,輸出6。
執行then1,輸出8。
好了,第一輪事件循環正式結束,這一輪的結果是輸出1,7,6,8。
2.第二輪時間循環從setTimeout1宏任務開始:
首先輸出2。接下來遇到了process.nextTick(),同樣将其分發到微任務Event Queue中,記為process2。
new Promise立即執行輸出4,then也分發到微任務Event Queue中,記為then2
宏任務Event Queue | 微任務Event Queue |
---|---|
已被取出 | process2 |
setTimeout2 | then2 |
如表所示:這一輪取出setTimeout1,分析發現兩個微任務,則先執行這兩個微任務。
3.第三輪宏任務setTimeout2開始執行
宏任務Event Queue | 微任務Event Queue |
---|---|
已被取出 | process3 |
已被取出 | then3 |
如表所示:這一輪取出setTimeout2,分析發現兩個微任務,則先執行這兩個微任務process3和then3。
輸出10。
輸出12。
第三輪事件循環結束,第三輪輸出9,11,10,12。
整段代碼,共進行了三次事件循環,完整的輸出為1,7,6,8,2,4,3,5,9,11,10,12。(請注意,node環境下的事件監聽依賴libuv與前端環境不完全相同,輸出順序可能會有誤差)
圖解js執行機制–事件循環
首先,整體的script(作為第一個宏任務)開始執行的時候,會把所有代碼分為同步任務、異步任務兩部分
同步任務會直接進入主線程依次執行
異步任務會再分為宏任務和微任務
宏任務進入到Event Table中,并在裡面注冊回調函數,每當指定的事件完成時,Event Table會将這個函數移到Event Queue中
微任務也會進入到另一個Event Table中,并在裡面注冊回調函數,每當指定的事件完成時,Event Table會将這個函數移到Event Queue中
當主線程内的任務執行完畢,主線程為空時,會檢查微任務的Event Queue,如果有任務,就全部執行,如果沒有就執行下一個宏任務
上述過程會不斷重複,這就是Event Loop,比較完整的事件循環