最近看到Event Loop這個詞出現的頻率有點高,因而查閱各方資料在此記錄一下。html
先不說概念,咱們來看段代碼:react
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');
複製這段代碼到控制檯,在Chrome會輸出以下結果segmentfault
Why?
若是想弄清楚緣由,就必須得弄清楚今天要提到的概念Event Loop。promise
棧
函數調用造成了一個棧幀。瀏覽器
function foo(b) { var a = 10; return a + b + 11; } function bar(x) { var y = 3; return foo(x * y); } console.log(bar(7)); // 返回 42
當調用 bar 時,建立了第一個幀 ,幀中包含了 bar 的參數和局部變量。當 bar 調用 foo 時,第二個幀就被建立,並被壓到第一個幀之上,幀中包含了 foo 的參數和局部變量。當 foo 返回時,最上層的幀就被彈出棧(剩下 bar 函數的調用幀 )。當 bar 返回的時候,棧就空了。網絡
堆
對象被分配在一個堆中,即用以表示一大塊非結構化的內存區域。多線程
隊列
一個 JavaScript 運行時包含了一個待處理的消息隊列。每個消息都關聯着一個用以處理這個消息的函數。併發
在事件循環期間的某個時刻,運行時從最早進入隊列的消息開始處理隊列中的消息。爲此,這個消息會被移出隊列,並做爲輸入參數調用與之關聯的函數。正如前面所提到的,調用一個函數老是會爲其創造一個新的棧幀。app
函數的處理會一直進行到執行棧再次爲空爲止;而後事件循環將會處理隊列中的下一個消息(若是還有的話)。異步
稍理解JavaScript的都知道JavaScript是單線程,即同一時間只能處理一件事情。JavaScript爲何不能是多線程呢,這樣就能夠同時處理多件事情提升效率。
JavaScript的宿主最開始自己就是瀏覽器,處理用戶的交互事件。做爲瀏覽器腳本,它只能一次作一件事情,假如用戶點擊一個按鈕的時候,須要刪除一個節點,而另外一段代碼此時又要添加這個節點,那JavaScript該如何處理,以誰爲準?
因此JavaScript在創造之初就考慮到了這點,也決定了它只能是單線程,這是它的核心特徵之一。
Event Loop
既然JavaScript是單線程的,那就意味着任務須要排隊,只有前一個任務執行完畢,下一個任務才能開始,因而就有了任務隊列。若是一個任務耗時很長,下面的任務就得一直等着,明顯不太合理,那麼可否先把耗時好久的任務先掛起來,先執行後面的任務,等IO設備返回的結果,再去執行以前掛着的任務。
因而任務就能夠分兩種:同步任務和異步任務
同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。
(1)全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。
(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重複上面的第三步。
異步任務指的是異步的代碼加入到任務隊列中,等待主線程通知執行
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲Event Loop!
Event loop:客戶端必須使用本章節中所描述的事件循環,來協調事件,用戶交互,腳本,呈現,網絡等等。 事件循環有兩種:用於瀏覽上下文的事件循環和用於 worker 的事件循環。
任務隊列分爲宏任務隊列(macro tasks) 和 微任務隊列(micro tasks)
如何判斷一段代碼是加入到宏任務隊列仍是微任務隊列?
每一個任務都由特殊任務源來定義。 來自同一個特殊任務源的全部任務都將發往特定事件循環。因此咱們能夠按照不一樣的來源進行分類,不一樣來源的任務都對應到不一樣的任務隊列中
(macro-task 宏任務)來源:I/O, setTimeout + setInterval + setImmediate, UI renderder ···
(micro-task 微任務)來源:Promise ,process.nextTick ,MutationObserver, Object.observe ···
Microtasks are usually scheduled for things that should happen straight after the currently executing script, such as reacting to a batch of actions, or to make something async without taking the penalty of a whole new task. The microtask queue is processed after callbacks as long as no other JavaScript is mid-execution, and at the end of each task. Any additional microtasks queued during microtasks are added to the end of the queue and also processed. Microtasks include mutation observer callbacks, and as in the above example, promise callbacks.
看下完整的執行過程:
• 代碼開始執行,JavaScript 引擎對全部的代碼進行區分。
• 同步代碼被壓入棧中,異步代碼根據不一樣來源加入到宏任務隊列尾部,或者微任務隊列的尾部。
• 等待棧中的代碼被執行完畢,此時通知任務隊列,執行位於隊列首部的宏任務。
• 宏任務執行完畢,開始執行其關聯的微任務。
• 關聯的微任務執行完畢,繼續執行下一個宏任務,直到任務隊列中全部宏任務被執行完畢。
•執行下一個任務隊列。
參考文檔: