這一次,徹底弄懂JS執行機制(Event Loop)

lily醬發表於2018-06-26

js單執行緒 

從接觸js開始,我們便知道js是單執行緒,大家有沒有想過為什麼js沒有被設計成多執行緒呢?因為多執行緒之間會共享執行資源,瀏覽器端的js會操作dom,多個執行緒肯定會帶來同步的問題,所以js核心是單執行緒的,來避免這個麻煩。 

堆(heap)、棧(stack)、佇列(queue)

  • 堆(heap) 

在js執行時,主要是存放物件的空間。

  • 棧(stack) 

棧中主要存放的是,各種函式執行(dom操作,ajax操作)、宣告的一些變數。我們這裡說的棧,程式碼的呼叫棧,是一個先進後出的結構。

  • 佇列(queue) 

存放的是各種非同步、回撥函式,是一個先進先出的佇列結構。

js任務

單執行緒意味著js任務需要排隊,如果前一個任務出現大量的耗時操作,後面的任務就得不到執行,這樣就會導致頁面出現“假死”,於是聰明的程式猿將js任務分為了兩類,同步任務和非同步任務

  • 同步任務 

在主執行緒排隊執行的任務,前一個任務執行完成後,再執行下一個任務,實現函式的層層呼叫。 

  • 非同步任務 

非同步任務會被主執行緒掛起啦,不會進入主執行緒,而是進入任務佇列,並且必須指定回撥函式,只有訊息佇列通知主執行緒,並且執行棧為空時,任務佇列的任務才會進入執行棧執行。

  •  巨集任務&微任務 

非同步任務在執行的時候,都是按照時間先後順序執行的嗎?NO!事實上非同步任務分為巨集任務和微任務。當執行棧中都執行完後,會優先清空微任務佇列,當微任務清空後,才會執行巨集任務佇列中的函式。 

巨集任務:setTimeout,、setInterval、setImmediate 

微任務:then、MutationObserver、MessageChannel、nextTick 

瀏覽器中的Event Loop 

同步和非同步任務分別進入不同的執行“場所”,同步的進入執行棧,非同步的進入Event Table並註冊函式。當指定的事情完成時,Event Table會將這個函式移入到Queue中(微任務移入到微任務佇列,巨集任務移入到巨集任務佇列)。當“執行棧”中的所有同步任務執行完畢,系統會優先讀取“微任務,按先進先出的順序,取出對應的任務,進入執行棧,開始執行;微任務清空後再讀取“巨集任務,按先進先出的順序,取出對應的任務,進入執行棧,開始執行。上述過程會不斷重複,就是常說當Event Loop(事件迴圈)。 

舉個? 

console.log(1)
setTimeout(() => {
  console.log('setTimeout')
})
Promise.resolve().then(() => {
  console.log('then')
}) 複製程式碼

先把同步程式碼放到執行棧執行,列印出1 

then放到微任務佇列,setTimeout放到巨集任務佇列

先清空微任務 列印出then 然後取出巨集任務佇列函式執行列印出setTimeout

列印結果是:1、then、setTimeout 

Node系統中的Event Loop 

nodejs也是單執行緒的,但是它的執行機制不同於瀏覽器。 

nodejs的Event Loop是基於libuv,libuv已經對Event Loop作出了實現。 

當nodejs啟動時,會初始化Event Loop,每一個Event Loop都會包含如下六個迴圈階段,並按順序執行。

 這一次,徹底弄懂JS執行機制(Event Loop)

  • timers:執行setTimeout,setInterval預定的callback; 
  • I/O callbacks:處理網路,流,tcp的錯誤callback 
  • idle,prepare:node內部使用 
  • poll:執行poll中的i/o佇列檢查定時器是否到時(fs的i/o操作) 
  • check:存放setImmeditate 
  • close cllbacks:關閉的回撥,如socket.on('close')  

Node中的Event Loop 同步的進入執行棧,非同步任務分別進入各自對用的迴圈階段,等待執行。當同步執行棧執行會切換到佇列執行階段,這時候會先去清空微任務(有階段切換就會清空微任務),然後再從上到下執行上面當六個迴圈階段。 

?思考一下,如下程式碼在node中執行順序 

setTimeout(() => {
  console.log('setTimeout')
})
setImmediate(() => {
  console.log('setImmediate')
}) 複製程式碼

是不是setTimeout先列印出來呢?因為setTimeout在最上面執行,而setImmediate屬於check階段的,在後面。 但是,結果告訴我們,不一定。有木有很震驚?,說好的按順序執行呢,那為什麼不一定呢,其實是,node啟動需要時間,當啟動完成後,timers可能到時間了,如果到時間了,就會執行timers,但如果沒到時間,就會跳過timers往下執行,這樣就會等到下次迴圈當時候才會執行timers對應當函式。so,它們誰先執行,這取決於node的執行時間,明白了吧。 

?看看下程式碼,它會怎麼執行呢? 

 setTimeout(() => {
  console.log('setTimeout1')
  process.nextTick(() => {
    console.log('nextTick1')
  })
})
process.nextTick(() => {
  console.log('nextTick1')
  setTimeout(() => {
    console.log('setTimeout2')
  })
}) 複製程式碼

node在執行的時候 ,不是取出一個 放到棧裡執行,而是把佇列裡到達時間的全部清空,再執行微任務。 

所以正確答案是:nextTick1、、setTimeout1、setTimeout2、nextTick1。 

以上,就是事件環(Event Loop)在瀏覽器和node環境下的一些區別。   



相關文章