我們繼續來講JavaScript中的事件循環機制,第一個講的JavaScript事件循環機制並沒有講完,我們現在繼續接著講,對JavaScript事件循環機制的繼續來看本篇文章吧!
在上一篇文章裡面我大致介紹了JavaScript的事件循環機制,但最後還留下了一段程式碼和幾個問題。
那我們先從這段程式碼開始看哇
(function test() { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3); })()
在這段程式碼裡面,setTimeout和Promise都被稱之為任務來源,來自不同任務來源的回呼函數會被放進不同的任務佇列裡面。
setTimeout的回呼函數被放進setTimeout的任務佇列之中。而對於Promise,它的回呼函數並不是傳進去的executer函數,而是其非同步執行的then方法裡面的參數,被放進Promise的任務佇列之中。也就是說Promise的第一個參數並不會被放進Promise的任務佇列之中,而會在目前隊列就執行。
其中setTimeout和Promise的任務佇列叫做macro-task(巨集任務),當然如我們所想,還有micro-task(微任務)。
macro-task包含:script(整體程式碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task包含:process.nextTick, Promises, Object.observe, MutationObserver
其中上面的setImmediate和process.nextTick是Node .JS裡面的API,瀏覽器裡面並沒有,這裡就當舉例,不必糾結具體是怎麼實現的。
事件循環的順序是從script開始第一次循環,隨後全局上下文進入函數調用棧,碰到macro-task就將其交給處理它的模組處理完之後將回調函數放進macro -task的佇列之中,碰到micro-task也是將其回呼函數放進micro-task的佇列之中。直到函式呼叫堆疊清空只剩下全域執行上下文,然後開始執行所有的micro-task。當所有可執行的micro-task執行完畢之後。迴圈再次執行macro-task中的一個任務佇列,執行完之後再執行所有的micro-task,就這樣一直循環。
分析執行過程
下面分析的想法依照波同學先前所寫的深入核心,詳解事件循環機制中的思路進行分析。
以先前的栗子作為分析的對象,來分析事件循環機制究竟是怎麼執行程式碼的
(function test() { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3); })()
注意下面所有圖中的setTimeout任務隊伍和最後的函數調用堆疊中存放的都是setTimeout的回呼函數,並不是整個setTimeout計時器。
1.首先,script任務來源先執行,全域上下文入堆疊。
2.script任務來源的程式碼在執行時遇到setTimeout,作為一個macro-task,將其回呼函數放入自己的佇列之中。
3.script任務來源的程式碼在執行時遇到Promise實例。 Promise建構子中的第一個參數是在目前任務直接執行不會被放入佇列之中,因此此時輸出 1 。
4.在for迴圈裡面遇到resolve函數,函數入堆疊執行後出棧,此時Promise的狀態變成Fulfilled。程式碼接著執行遇到console.log(2),輸出2。
5.接著執行,程式碼遇到then方法,其回呼函數作為micro-task入棧,進入Promise的任務佇列之中。
6.程式碼接著執行,此時遇到console.log(3),輸出3。
7.输出3之后第一个宏任务script的代码执行完毕,这时候开始开始执行所有在队列之中的micro-task。then的回调函数入栈执行完毕之后出栈,这时候输出5
8.这时候所有的micro-task执行完毕,第一轮循环结束。第二轮循环从setTimeout的任务队列开始,setTimeout的回调函数入栈执行完毕之后出栈,此时输出4。
总结
总的来说就是:
1、不同的任务会放进不同的任务队列之中。
2、先执行macro-task,等到函数调用栈清空之后再执行所有在队列之中的micro-task。
3、等到所有micro-task执行完之后再从macro-task中的一个任务队列开始执行,就这样一直循环。
4、当有多个macro-task(micro-task)队列时,事件循环的顺序是按上文macro-task(micro-task)的分类中书写的顺序执行的。
测试
说到这里,我们应该都明白了,下面是一个复杂的代码段(改自深入核心,详解事件循环机制),里面有混杂着的micro-task和macro-task,自己画图试试流程哇,然后再用node执行看看输出的顺序是否一致。
console.log('golb1'); setImmediate(function() { console.log('immediate1'); process.nextTick(function() { console.log('immediate1_nextTick'); }) new Promise(function(resolve) { console.log('immediate1_promise'); resolve(); }).then(function() { console.log('immediate1_then') }) }) setTimeout(function() { console.log('timeout1'); process.nextTick(function() { console.log('timeout1_nextTick'); }) new Promise(function(resolve) { console.log('timeout1_promise'); resolve(); }).then(function() { console.log('timeout1_then') }) setTimeout(function() { console.log('timeout1_timeout1'); process.nextTick(function() { console.log('timeout1_timeout1_nextTick'); }) setImmediate(function() { console.log('timeout1_setImmediate1'); }) }); }) new Promise(function(resolve) { console.log('glob1_promise'); resolve(); }).then(function() { console.log('glob1_then') }) process.nextTick(function() { console.log('glob1_nextTick'); })
讲到这里我们的细说JavaScript事件循环机制也就正式讲完了,看不懂了两篇结合起来看看,练练即可!
先看看我吧:
以上是細說JavaScript事件循環機制-第二講的詳細內容。更多資訊請關注PHP中文網其他相關文章!