Javascript中的事件循環機制,很多文章都只說了Javascript的事件分為同步任務和非同步任務遇到同步任務就放在執行棧中執行,而碰到非同步任務就放到任務佇列之中,等到執行棧執行完畢之後再去執行任務佇列之中的事件。本篇文章講的很不錯!我們一起來看看吧!我們直接進入正題吧!
函數呼叫堆疊與任務佇列
Javascript有一個main thread 主程式和call-stack(一個呼叫堆疊),在對一個呼叫堆疊中的task處理的時候,其他的都要等。當在執行過程中遇到一些類似setTimeout等非同步操作的時候,會交給瀏覽器的其他模組(以webkit為例,是webcore模組)進行處理,當到達setTimeout指定的延時執行的時間之後, task(回呼函數)會放入到任務佇列之中。一般不同的非同步任務的回呼函數會放入不同的任務佇列之中。等到呼叫堆疊中所有task執行完畢之後,接著去執行任務佇列之中的task(回呼函數)。
用Philip Roberts的演講《Help, I'm stuck in an event-loop》之中的一張圖表示就是
在上圖中,呼叫堆疊中遇到DOM操作、ajax請求以及setTimeout等WebAPIs的時候就會交給瀏覽器核心的其他模組進行處理,webkit核心在Javasctipt執行引擎之外,有一個重要的模組是webcore模組。圖中WebAPIs提到的三種API,webcore分別提供了DOM Binding、network、timer模組來處理底層實作。等到這些模組處理完這些操作的時候將回呼函數放入任務佇列中,之後等棧中的task執行完之後再去執行任務佇列之中的回呼函數。
從setTimeout看事件循環機制
#下面用Philip Roberts的演講中的一個栗子來說明事件循環機制究竟是怎麼執行setTimeout的。
首先main()函數的執行上下文入堆疊
#程式碼接著執行,遇到console.log( 'Hi'),此時log('Hi')入棧,console.log方法只是一個webkit核心支援的普通的方法,所以log('Hi')方法立即被執行。此時輸出’Hi’。
當遇到setTimeout的時候,執行引擎會將其加入堆疊中。
呼叫堆疊發現setTimeout是先前提到的WebAPIs中的API,因此將其出棧之後將延時執行的函數交給瀏覽器的timer模組進行處理。
timer模組去處理延時執行的函數,此時執行引擎接著執行將log(‘SJS’)加入堆疊中,此時輸出’SJS’。
當timer模組中延時方法規定的時間到了之後就將其放入到任務佇列之中,此時呼叫堆疊中的task已經全部執行完畢。
呼叫堆疊中的task執行完畢之後,執行引擎會接著看執行任務佇列中是否有需要執行的回調函數。這裡的cb函數被執行引擎加入到呼叫堆疊中,接著執行裡面的程式碼,輸出’there’。等到執行結束後再出棧。
小結
上面的這一個流程解釋了當瀏覽器遇到setTimeout之後究竟是怎麼執行的,相類似的還有前面圖中提到的另外的API以及另外一些非同步的操作。
總結上文說的,主要就是以下幾點:
1、所有的程式碼都要透過函數呼叫堆疊中呼叫執行。
2、當遇到前文提到的APIs的時候,會交給瀏覽器核心的其他模組進行處理。
3、任務佇列中存放的是回呼函數。
4、等到呼叫堆疊中的task執行完之後再回去執行任務佇列之中的task。
測驗
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(new Date, i); }, 1000); } console.log(new Date, i);
這段程式碼是我從網路前不久的一篇文章80%應徵者都不及格的JS 面試題中找到的,現在我們就分析這段程式碼究竟是怎麼輸出最後文章中所說的最後的執行狀態:
40% 的人會描述為: 5 -> 5,5,5,5,5,即第1 個5 直接輸出,1 秒之後,輸出5 個5;
1、首先i=0時,滿足條件,執行堆疊執行循環體裡面的程式碼,發現是setTimeout,將其出棧之後把延時執行的函數交給Timer模組處理。
2、當i=1,2,3,4時,都滿足條件,情況和i=0時相同,因此timer模組裡面有5個相同的延遲執行的函數。
3、當i=5的時候,不滿足條件,因此for循環結束,console.log(new Date, i)入棧,此時的i已經變成了5。因此輸出5。
4、此時1s已經過去,timer模組將5個回呼函數依照註冊的順序傳回給任務佇列。
5、執行引擎去執行任務佇列中的函數,5個function依序入棧執行之後再出棧,此時的i已經變成了5。因此幾乎同時輸出5個5。
6、因此等待的1s的時間其實只有輸出第一個5之後需要等待1s,這1s的時間是timer模組需要等到的規定的1s時間之後才將回調函數交給任務隊列。等執行棧執行完畢之後再去執行任務對列中的5個回呼函數。這段期間是不需要等待1s的。因此輸出的狀態為:5 -> 5,5,5,5,5,即第1個5 直接輸出,1s之後,輸出5個5;
##問題
看到這裡,對事件循環機制有了一個大概的了解了,可是細想,其中還有一些另外值得深入的問題。下面透過一個栗子來說明:
(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); })()
相關推薦:
以上是細說JavaScript事件循環機制-第一講的詳細內容。更多資訊請關注PHP中文網其他相關文章!