JS事件循環(Event Loop)機制

前言

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

  2. 爲什麼非阻塞?由於單線程意味着任務須要排隊,任務按順序執行,若是一個任務很耗時,下一個任務不得不等待。因此爲了不這種阻塞,咱們須要一種非阻塞機制。這種非阻塞機制是一種異步機制,即須要等待的任務不會阻塞主執行棧中同步任務的執行。這種機制是以下運行的:promise

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

    用一張圖展現這個過程:瀏覽器


     
    Markdown

正文

1.macro task與micro task

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

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

microtask和macotask執行規則:異步

  • macrotask按順序執行,瀏覽器的ui繪製會插在每一個macrotask之間
  • microtask按順序執行,會在以下狀況下執行:
    • 每一個callback以後,只要沒有其餘的JS在主執行棧中
    • 每一個macrotask結束時

下面來個簡單例子:oop

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);

一步一步分析以下:ui

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

再來一個複雜的例子:spa

// 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
相關文章
相關標籤/搜索