JS事件迴圈,瞭解一下?

志如發表於2019-02-23

在理解事件迴圈之前,我總會遇到一些奇奇怪怪的問題:比如明明已經調介面拿到了資料,可是跟在調資料之後的操作卻沒有正常執行;又或者不知道為啥,程式碼裡非得加個setTimeout才能正常跑通;特別是在運用Promise的時候,更是有各種問題百思不得解。遇上問題要解決,更要知道問題產生的原因,這樣才能hold住全場!

廢話不多說了,先來看一段程式碼

console.log('start');

setTimeout(function(){
   console.log('setTImeout1') 
},0);

new Promise(function(resolve,reject){
    console.log('resolve')
    setTimeout(function(){
        console.log('setTimeout2')
    },200);
    resolve()
}).then(function(){
    console.log('then')
});

setTimeout(function(){
    console.log('setTimeout3')
},0);

console.log('end');
複製程式碼

結果是start resolve end then setTimeout1 setTimeout3 settimeout2

在分析結果之前,我先來科普幾個概念,這些概念的表述不一定與標準完全對應,但是可以幫助你更容易理解JS的事件機制

  • 巨集任務(macro-task):包括js整體程式碼,setTimeout,setInterval,setImmediate ,I/O, UI renderder等
  • 微任務(micro-task):包括Promise,Object.observe,process.nextTick,MutationObserver等
  • 呼叫棧:js被載入進來之後,會從上至下讀取程式碼,同步程式碼被立即執行,而非同步程式碼被加入事件佇列中
  • 事件佇列:一些沒有被立即執行的程式碼被新增到事件佇列中,佇列是一種先進先出的資料結構,也就是說,先加入事件佇列的任務會被優先執行

我們知道,js是單執行緒的,這就是說,只有一個主執行緒,主執行緒會自上而下依次執行呼叫棧中的事件。任務佇列中的程式碼被載入到函式呼叫棧中去執行。當前的巨集任務佇列中的程式碼執行完畢後,會執行本次巨集任務佇列中分發到微任務佇列中的程式碼。然後執行下一個巨集任務佇列中的程式碼,依次迴圈。

這裡要提一點容易誤解的地方,setTimeout函式本身,其實是立即執行的,它內部的任務,才會被分發到任務佇列中延時執行。


  • 程式碼被載入後,全域性上下文進入函式呼叫棧,緊接著,‘start’被執行
  • 遇到setTimeout的時候,新建了一個巨集任務佇列,函式內的任務被分發這個佇列中等待執行
  • 此時遇到了Promise,注意,Promise中的第一個function中的程式碼會立馬開始執行,遇到resolve或者reject後,then方法中函式會被分發到本次事件迴圈的微任務佇列中等待執行。所以這裡立馬列印出了'resolve'。遇到setTimeout2後,同樣新建了一個巨集任務佇列,其中的函式被分發到了這個新的巨集任務佇列中,then方法中的操作被分發到了微任務佇列中等待
  • 程式碼繼續往下,遇到'setTimeout3'後再次新建了一個新的巨集任務佇列
  • 'end'被立即執行。此時有三個巨集任務佇列,一個微任務佇列
  • 微任務佇列中的操作被執行,也就是列印出了‘then’,此時,第一輪的事件迴圈結束。
  • 第一輪的事件迴圈結束,開始下一輪的事件迴圈,依次執行每個巨集任務佇列中的內容,我們這裡巨集任務佇列中的函式比較簡單,都是console操作,所以並沒有再分發新的任務佇列,但是由於第二個setTimeout設定了200毫秒的延時,所以‘setTimeout2’被最後列印。

說到這裡,你基本上對事件迴圈有個大致的瞭解了。之前有個同學問過我一個問題,點選輪播圖下一頁,但是頁面沒有反應,程式碼是這樣的:

JS事件迴圈,瞭解一下?
這個函式是點選下一頁的按鈕後輪播圖轉動,他在getNextPhoto函式中調介面獲取了下個頁面的資料,goToPage函式裡是讓輪播圖切換的操作。相信如果你讀懂了這篇文章,就會知道問題出在了哪裡。顯然獲取資料ajax是個非同步操作,他被分發到了事件佇列中等待執行,所以還沒等資料回來,翻頁的操作已經開始執行,由於沒有資料,並沒有按預期效果顯示。解決方法就是在getNextPhoto函式裡調介面拿到資料之後,再通知去執行goToPage操作,問題就解決了。

作為一個前端菜鳥,希望得到各位大神的批評指正!

相關文章