php event loop,理解javascript中的事件循环(Event Loop)
背景
在研究js的異步的實現方式的時候,發現了JavaScript 中的 macrotask 和 microtask 的概念。在查閱了一番資料之后,對其中的執行機制有所了解,下面整理出來,希望可以幫助更多人。
先了解一下js的任務執行機制
首先,javascript是單線程的,所以只能通過異步解決性能問題(否則,如果前面一個任務阻塞了,那么后續的任務都要等待,這種效果是無法接受的)。js在執行代碼時存在著兩個比較重要的東西:執行棧和任務隊列,這兩個東西都是用來存儲任務的,區別在于:執行棧里面存著的都是同步任務,也就是要按順序執行的任務;而任務隊列中存著的是一些異步任務,這些異步任務一定要等到執行棧清空后才會執行(這句話很重要)。關于任務隊列,它還分成兩種,一種叫作macrotask queue(姑且這么命名,因為嚴格來說規范中只有說task,并沒有提到macrotask這個概念。這里為了容易區分,可以理解為macrotask=task!=microtask),另一種叫作microtask queue。如果同時考慮node環境和瀏覽器環境的話,這兩種任務分別對應以下api:
microtasks:
process.nextTick
promise
Object.observe
MutationObserver
macrotasks:
setTimeout
setInterval
setImmediate
I/O
UI渲染
script標簽中的整體代碼
javascript在執行時,先從 macrotasks 隊列開始執行,取出第一個 macrotask 放入執行棧執行,在執行過程中,如果遇到 macrotask,則將該 macrotask 放入 macrotask 隊列,繼續運行執行棧中的后續代碼。如果遇到microtask,那么將該microtask放入microtask隊列,繼續向下運行執行棧中的后續代碼。當執行棧中的代碼全部執行完成后,從microtasks隊列中取出所有的microtask放入執行棧執行。執行完畢后,再從macrotasks 隊列取出下一個macrotask放入執行棧。然后不斷重復上述流程。這一過程也被稱作事件循環(Event Loop)。
javascript就是通過這種機制來實現異步的。主線程會暫時存儲I/O等異步操作,直接向下執行,當某個異步事件觸發時,再通知主線程執行相應的回調函數,通過這種機制,javascript避免了單線程中異步操作耗時對后續任務的影響。
圖解事件循環流程
根據圖中描述,一次事件循環的執行步驟如下:
1、從macrotask queue中取出最早的任務
2、在執行棧中執行第一步取出的任務
如果任務中存在microtask,將其壓入到microtask queue中
如果任務中存在macrotask,將其壓入到macrotask queue中
直到執行完畢
3、執行棧設置為null
4、從macrotask queue中刪除執行過的macrotask
5、取出microtask queue中的全部任務,放入執行棧,
如果任務中存在microtask,將其壓入到microtask queue中
如果任務中存在macrotask,將其壓入到macrotask queue中
注意:這里產生的microtask(也就是microtask產生的microtask )也會在這一步驟中執行。
直到當前microtask queue為空,此步驟結束。
6、執行第一步的操作
實例驗證
我們執行如下一段代碼,用上面的思路執行,看一下結果是否和預期的一致。
console.log('start')
const interval = setInterval(() => {
console.log('setInterval')
}, 0)
setTimeout(() => {
console.log('setTimeout 1')
Promise.resolve()
.then(() => {
console.log('promise 3')
})
.then(() => {
console.log('promise 4')
})
.then(() => {
setTimeout(() => {
console.log('setTimeout 2')
Promise.resolve()
.then(() => {
console.log('promise 5')
})
.then(() => {
console.log('promise 6')
})
.then(() => {
clearInterval(interval)
})
}, 0)
})
}, 0)
Promise.resolve()
.then(() => {
console.log('promise 1')
})
.then(() => {
console.log('promise 2')
})
按照上面的思路,我們來理一下,預測一下執行結果,看看實際效果是否是這樣的。
執行流程:
第一輪:
1、首先這一整段js代碼作為一個macrotask先被執行
2、遇到console.log('start'),輸出start
3、遇到setInterval,回調函數作為macrotask壓入到macrotask queue中,
此時macrotask queue:[setInterval]
4、遇到setTimeout,回調函數作為macrotask壓入到macrotask queue中,
此時macrotask queue:[setInterval,setTimeout1]
5、遇到Promise,并且調用了resolve方法,觸發了回調,回調作為microtask壓入到microtask queue中
此時microtask queue:[promise 1,promise 2]
6、執行棧為空,將microtask queue中的任務放入執行棧
7、執行microtask queue中Promise的回調任務,分別打印promise 1,promise 2
8、執行棧為空,microtask queue為空,開始下一輪事件循環
目前的console中打印內容:
start
promise 1
promise 2
目前macrotask queue:[setInterval,setTimeout1]
第二輪:
1、從macrotask queue中取出最早的任務,這里對應的是第一輪中第3步的回調函數:console.log('setInterval'),輸出setInterval
2、setInterval的回調函數作為macrotask壓入到macrotask queue中
此時macrotask queue:[setTimeout1,setInterval]
3、執行棧為空,microtask queue為空,開始下一輪事件循環
目前的console中打印內容:
start
promise 1
promise 2
setInterval
目前macrotask queue:[setTimeout1,setInterval]
第三輪:
1、從macrotask queue中取出最早的任務,目前是setTimeout1的回調,將取出的任務放入執行棧執行
2、遇到console.log('setTimeout 1'),輸出setTimeout 1
3、遇到Promise,并且調用了resolve方法,觸發回調,回調作為microtask壓入到microtask queue中
此時microtask queue:[promise 3,promise 4,() => {setTimeout 2}]
4、執行棧為空,將microtask queue中的任務放入執行棧
5、執行microtask queue中Promise的回調任務:
輸出promise 3
輸出promise 4
將setTimeout 2壓入macrotask queue
6、執行棧為空,microtask queue為空,開始下一輪事件循環
目前的console中打印內容:
start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
目前macrotask queue:[setInterval,setTimeout2]
第四輪:
1、從macrotask queue中取出最早的任務,這里對應的是setInterval,輸出setInterval
2、setInterval的回調函數作為macrotask壓入到macrotask queue中
此時macrotask queue:[setTimeout2,setInterval]
3、執行棧為空,microtask queue為空,開始下一輪事件循環
目前的console中打印內容:
start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
目前macrotask queue:[setTimeout2,setInterval]
第五輪:
1、從macrotask queue中取出最早的任務,目前是setTimeout2的回調,將取出的任務放入執行棧執行
2、遇到console.log('setTimeout 2')輸出setTimeout 2
3、遇到Promise,并且調用了resolve方法,觸發回調,回調作為microtask壓入到microtask queue中
此時microtask queue:[promise 5,promise 6,() => {clearInterval}]
4、執行棧為空,將microtask queue中的任務放入執行棧
5、執行microtask queue中Promise的回調任務:
輸出promise 5
輸出promise 6
clearInterval清空setInterval計時器
6、執行棧為空,microtask queue為空,macrotask queue為空,任務結束。
最終的console中打印內容:
start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setTimeout 2
promise 5
promise 6
通過圖片可以看到,結果跟我們的預期一致,在promise2的后面作為方法的返回值,多打印了一個undefined,這個應該好理解的。
這里面有個小問題,就是在不同的環境下(node/瀏覽器),promise4后面的setInterval表現可能會有差異,這里可能跟setTimeout和setInterval的最小間隔有關,雖然我們寫成0ms,但實際上這個最小值是有限制的,現階段不同組織和不同的js引擎實現機制存在差異,不過這個問題不在本次討論范圍之內了。如果我們將上述代碼中setInterval的間隔設置為10,那么整個執行流程將嚴格符合我們的預期。
有什么用?
后續我們在代碼中使用Promise,setTimeout時,思路將更加清晰,用起來更佳得心應手。
在閱讀一些源碼時,對于一些setTimeout相關的騷操作可以理解的更加深入。
理解javascript中的任務執行流程,加深對異步流程的理解,少犯錯誤。
總結
js事件循環總是從一個macrotask開始執行
一個事件循環過程中,只執行一個macrotask,但是可能執行多個microtask
執行棧中的任務產生的microtask會在當前事件循環內執行
執行棧中的任務產生的macrotask要在下一次事件循環才會執行
總結
以上是生活随笔為你收集整理的php event loop,理解javascript中的事件循环(Event Loop)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php遍历中记录所有,php遍历类中包含
- 下一篇: oracle 容器切换,oracle12