JS事件循環(Event Loop)機制


前言

眾所周知,為了與瀏覽器進行交互,Javascript是一門非阻塞單線程腳本語言。
  1. 為何單線程? 因為如果在DOM操作中,有兩個線程一個添加節點,一個刪除節點,瀏覽器並不知道以哪個為准,所以只能選擇一個主線程來執行代碼,以防止沖突。雖然如今添加了webworker等新技術,但其依然只是主線程的子線程,並不能執行諸如I/O類的操作。長期來看,JS將一直是單線程。

  2. 為何非阻塞?因為單線程意味着任務需要排隊,任務按順序執行,如果一個任務很耗時,下一個任務不得不等待。所以為了避免這種阻塞,我們需要一種非阻塞機制。這種非阻塞機制是一種異步機制,即需要等待的任務不會阻塞主執行棧中同步任務的執行。這種機制是如下運行的:

    • 所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)
    • 等待任務的回調結果進入一種任務隊列(task queue)
    • 當主執行棧中的同步任務執行完畢后才會讀取任務隊列,任務隊列中的異步任務(即之前等待任務的回調結果)會塞入主執行棧,
    • 異步任務執行完畢后會再次進入下一個循環。此即為今天文章的主角事件循環(Event Loop)

    用一張圖展示這個過程:


     
    Markdown

正文

1.macro task與micro task

在實際情況中,上述的任務隊列(task queue)中的異步任務分為兩種:微任務(micro task)宏任務(macro task)

  • micro task事件:Promises(瀏覽器實現的原生Promise)MutationObserverprocess.nextTick
    <br />
  • macro task事件:setTimeoutsetIntervalsetImmediateI/OUI rendering
    這里注意:script(整體代碼)即一開始在主執行棧中的同步代碼本質上也屬於macrotask,屬於第一個執行的task

microtask和macotask執行規則:

  • macrotask按順序執行,瀏覽器的ui繪制會插在每個macrotask之間
  • microtask按順序執行,會在如下情況下執行:
    • 每個callback之后,只要沒有其他的JS在主執行棧中
    • 每個macrotask結束時

下面來個簡單例子:

console.log(1);

setTimeout(function() {
  console.log(2);
}, 0);

new Promise(function(resolve,reject){
    console.log(3)
    resolve()
}).then(function() {
  console.log(4);
}).then(function() {
  console.log(5);
});

console.log(6);

一步一步分析如下:

  • 1.同步代碼作為第一個macrotask,按順序輸出:1 3 6
  • 2.microtask按順序執行:4 5
  • 3.microtask清空后執行下一個macrotask:2

再來一個復雜的例子:

// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

// Let's listen for attribute changes on the
// outer element
new MutationObserver(function() {
  console.log('mutate');
}).observe(outer, {
  attributes: true
});

// Here's a click listener…
function onClick() {
  console.log('click');

  setTimeout(function() {
    console.log('timeout');
  }, 0);

  Promise.resolve().then(function() {
    console.log('promise');
  });

  outer.setAttribute('data-random', Math.random());
}

// …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);

假設我們創建一個有里外兩部分的正方形盒子,里外都綁定了點擊事件,此時點擊內部,代碼會如何執行?一步一步分析如下:

  • 1.觸發內部click事件,同步輸出:click
  • 2.將setTimeout回調結果放入macrotask隊列
  • 3.將promise回調結果放入microtask
  • 4.將Mutation observers放入microtask隊列,主執行棧中onclick事件結束,主執行棧清空
  • 5.依序執行microtask隊列中任務,輸出:promise mutate
  • 6.注意此時事件冒泡,外部元素再次觸發onclick回調,所以按照前5步再次輸出:click promise mutate(我們可以注意到事件冒泡甚至會在microtask中的任務執行之后,microtask優先級非常高)
  • 7.macrotask中第一個任務執行完畢,依次執行macrotask中剩下的任務輸出:timeout timeout


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM