JS事件迴圈EventLoop初探

DarK-AleX發表於2018-01-30

概念

js是基於單執行緒執行的,而一些特定事件又是非同步執行的,所以這種單執行緒+非同步的執行方式一定是事件驅動的 而一般瀏覽器環境下有這樣幾種執行緒。

js引擎執行緒 (解釋執行js程式碼、使用者輸入、網路請求)主執行緒

GUI執行緒 (繪製使用者介面、與js主執行緒是互斥的)先繪製dom再繪製css

http網路請求執行緒 (處理使用者的get、post等請求,等返回結果後將回撥函式推入任務佇列)

定時觸發器執行緒 (setTimeout、setInterval等待時間結束後把執行函式推入任務佇列中)

瀏覽器事件處理執行緒(將click、mouse等互動事件發生後將這些事件放入事件佇列中)

上一張經典的eventLoop圖,瞭解幾個基本概念

JS事件迴圈EventLoop初探
1,棧(stack):佇列優先,由作業系統自動分配釋放 ,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。
2.堆(heap):先進後出;動態分配的空間
JavaScript中的變數分為基本型別和引用型別。基本型別就是儲存在棧記憶體中的簡單資料段,而引用型別指的是那些儲存在堆記憶體中的物件
這裡引入一個概念(圖上沒有),就是js中的task和microTask 一般來講,我們把setTimeout,setInterval,瀏覽器的dom操作,使用者點選等互動事件歸結為task,而Promise callback Mutation callback還有nextTick我們歸結為microTask,

那問題就來了,js中task和microTask執行順序是怎樣的

由於js是單執行緒,所有的多執行緒任務最後都要壓入主執行緒執行棧去執行,如圖所示,task會放入回撥佇列裡 由eventLoop取出依次執行。

這裡可以解釋一個小問題,如果多個setTimeout的情況下,執行時間不是特別的精確,setTimeout 它會在延遲時間結束後分配一個新的 task 至 event loop 中,而不是立即執行,所以 setTimeout 的回撥函式會等待前面的 task 都執行結束後再執行。

說到這裡好像還沒有microTask什麼事,那microTask在什麼時候執行呢,通俗的說,你可以將microTask理解為一個愛插隊的大媽,就是在一個task事件結束後,microTask就插入執行棧立即執行,執行順序是主執行緒執行棧的隊尾插入,callback quene之前

上一段程式碼吧,來直觀的瞭解一下執行順序

 console.log('start');
  
 setTimeout(function() {
    console.log('setTimeout1');
        setTimeout(function() {
           console.log('setTimeout2');
       },0);
    console.log('setTimeout3');
  }, 0);
  
  Promise.resolve().then(function() {
    console.log('promise1');
  }).then(function() {
   console.log('promise2');
 });
 
 console.log('end');
複製程式碼

最後結果是 1,start 2,end 3,promise1 4,promise2 5,setTimeout1,6,setTimeout3 6,setTimeout2

順序是,先執行列印start 然後列印edn 這時候microTask (promise)插隊進入 列印promise1,promise2, 然後執行setTimeout列印setTimeout1,setTimeout3,這裡可以看做
console.log('setTimeout1');
console.log('setTimeout3');
這兩句一起壓入執行棧,

setTimeout(function() {
        console.log('setTimeout2');
},0);
複製程式碼

進入task佇列,由EventLoop取出,所以最後執行順序是
1,start 2,end 3,promise1 4,promise2 5,setTimeout1,6,setTimeout3 6,setTimeout2

相關文章