衆所周知,Javascript是單線程語言, 這就意味着,全部的任務都必須按照順序執行,只有等前面的一個任務執行完畢了,下一個任務才能執行。若是前面一個任務耗時很長,後一個任務就得一直等着,所以,爲了實現主線程的不阻塞,就有了Event Loop。javascript
首先,咱們先了解一下同步任務和異步任務,同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。java
爲了更好的瞭解執行機制,看下圖node
以上圖說明主線程在執行的時候產生堆(內存分配)和堆棧(執行上下文),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
接着往下執行,執行環境棧中會建立一個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。異步
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——瀏覽器和Node和Event 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) }) })