Javascript中的執行機制——Event Loop

衆所周知,Javascript是單線程語言, 這就意味着,全部的任務都必須按照順序執行,只有等前面的一個任務執行完畢了,下一個任務才能執行。若是前面一個任務耗時很長,後一個任務就得一直等着,所以,爲了實現主線程的不阻塞,就有了Event Loop。javascript

一、javascript事件循環

首先,咱們先了解一下同步任務和異步任務,同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。java

爲了更好的瞭解執行機制,看下圖node

clipboard.png

以上圖說明主線程在執行的時候產生堆(內存分配)和堆棧(執行上下文),JavaScript是單線程的,意味着當執行環境的堆棧中的一個任務(task)在執行的時候,其它的任務都要處於等待狀態。當主進程執行到異步操做的時候就會將異步操做對應的task放到event table,指定的事情完成時,Event Table會將這個函數移入Event Queue。主線程內的任務執行完畢爲空,會去Event Queue讀取對應的函數,進入主線程執行,由於這個過程是不斷重複的,因此稱爲Event Loop(事件循環),接下來,咱們用幾個例子進行分析
eg1:segmentfault

console.log(1); 
setTimeout(function () { 
    console.log(2); 
})
 console.log(3);
 //執行結果:一、三、2

咱們來分析一下這段代碼,首先,根據執行上下文可知,執行環境棧中就有了一個task——console.log(1),輸出1。接着往下執行,由於setTimeout是異步函數,因此將setTimeout進入event table,註冊了一個回調函數在event queue,咱們暫且稱爲fun1,此時的流程以下圖:promise

clipboard.png

接着往下執行,執行環境棧中會建立一個console.log(3)的task,並執行它,輸出3,此時,執行環境已經沒有任務了,則去Event Queue讀取對應的函數,fun1被發現,進入主線程輸出2,整個過程已經完成,因此輸出的結果是一、三、2。
eg2:瀏覽器

setTimeout(function () {
      console.log(1)
   }, 3)
   setTimeout(function () {
      console.log(2)
    })
輸出2,1

咱們再來簡單的分析一下這個列子,咱們暫且稱第一個setTimeout爲Time1,第二個爲Time2。因爲兩個都是異步函數,按照執行順序,先將Time放到event Table,接着將Time移到event Table,由於Time在event Table指定要3秒後才執行,因此Time2先於Time1到註冊回調函數到event queue,因此輸出的結果是2,1。異步

二、macro-task(宏任務)、micro-task(微任務)

MacroTask: script(總體代碼), setTimeout, setInterval, setImmediate(node獨有), I/O, UI rendering
MicroTask: process.nextTick(node獨有), Promises, Object.observe(廢棄), MutationObserver

任務又分爲宏任務和微任務兩種,在同一個上下文中,總的執行順序爲「同步代碼—>microTask—>macroTask」,根據上面event loop的流程圖,咱們用列子來作進一步的瞭解:
eg1:函數

setTimeout(function () {
       console.log(1);
   },0);
   console.log(2);
   process.nextTick(() => {
       console.log(3);
   });
   new Promise(function (resolve, rejected) {
       console.log(4);
       resolve()
   }).then(res=>{
       console.log(5);
   })
   setImmediate(function () {
       console.log(6)
   })
   console.log('end');
    //輸出二、四、end、三、五、一、6

本例參考《JavaScript中的執行機制》,裏面有詳細的解釋,你們能夠參考下。oop

三、優先級

咱們將上面的例子稍微改一下,將process.nextTick移到promise的後面,看下面的代碼:post

setTimeout(function () {
       console.log(1);
   },0);
   console.log(2);
   new Promise(function (resolve, rejected) {
       console.log(4);
       resolve()
   }).then(res=>{
       console.log(5);
   })
 process.nextTick(() => {
       console.log(3);
   });
   setImmediate(function () {
       console.log(6)
   })
   console.log('end');

按照前面的分析,先執行同步代碼,先輸出「2,4,end」;而後是微任務promise輸出5,process.nextTick輸出3;最後的宏任務輸出1,6。因此結果爲2,4,end,5,3,1,6,而後事實並不是如此,結果仍是輸出二、四、end、三、五、一、6,這是由於process.nextTick註冊的函數優先級高於Promise**。
關於Event Loop的其餘特殊狀況,你們可參考文章一篇文章教會你Event loop——瀏覽器和NodeEvent Loop的規範和實現,裏面有更詳細的介紹。

四、補充

console.log(1)
    
    setTimeout(() => {
        console.log(2)
        new Promise(resolve => {
            console.log(4)
            resolve()
        }).then(() => {
            console.log(5)
        })
        setTimeout(() => {
            console.log(999)
        
        })
    })
    
    new Promise(resolve => {
        console.log(7)
        resolve()
    }).then(() => {
        console.log(8)
    })
    
    setTimeout(() => {
        console.log(9)
        new Promise(resolve => {
            console.log(11)
            resolve()
        }).then(() => {
            console.log(12)
        })
    })
相關文章
相關標籤/搜索