js內部事件機制–單執行緒原理

寒鬆發表於2018-07-01

原文地址:https://www.xingkongbj.com/blog/js/event-loop.html

http://www.haorooms.com/post/js_xiancheng

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

任務佇列

  • 主執行緒:正在執行的程式碼,會生成函式呼叫棧。
  • macro-task(巨集任務,新名:task)包括:script(整體程式碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
  • micro-task(微任務,新名:jobs)包括: process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性,佇列中只能有一個)

任務分類

  • 同步任務,語句只按語句先後順序執行,前面未執行完,不會執行後面語句。
  • 非同步任務,語句不在語句先後順序上執行,執行到該程式碼時,加入到相應任務佇列,延後執行。

單執行緒

主執行緒從 script (整體程式碼)開始第一次迴圈。之後全域性上下文進入函式呼叫棧。直到呼叫棧清空(只剩全域性),然後執行所有的 jobs。當所有可執行的 jobs 執行完畢之後。迴圈再次從 task 開始,找到其中一個任務佇列執行完畢,然後再執行所有的 jobs,這樣一直迴圈下去。

注意事項

  • setTimeout 最小間隔不能低於 4 毫秒,否則會自動增加。
  • DOM 的渲染每 16 毫秒執行一次,因為顯示器是 60 Hz,16ms 重新整理一次。
  • process.nextTick 任務會在 jobs 裡單獨維護一個佇列,並且在其他 jobs 任務之前執行。
  • 冒泡事件會直接在子元素事件執行完成後,插入在主執行緒中。如果主執行緒不為空,那麼會優先於 jobs 執行。

經典示例

示例詳解:https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

通過滑鼠點選

<div class="outer">
  <div class="inner"></div>
</div>

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

// 輸出結果
click
mutate
click
mutate
promise
promise
timeout
timeout

進階–通過js執行

<div class="outer">
  <div class="inner"></div>
</div>

// 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);
inner.click();

// 輸出結果
click
click
mutate
promise
promise
timeout
timeout

由於點選事件是 js 執行的,inner 的 onClick 函式執行完成時,inner.click() 語句的作用域還沒有退棧,主執行緒呼叫棧不是空的,導致 jobs 佇列任務不會執行,mutate 和 promise 語句都未能在事件迴圈中執行到。從而執行了 outer 的 onClick 函式。outer 的 onClick 函式執行完成後,inner.click() 語句才退棧,繼而執行 jobs 的任務。

只有一個 mutate 是由於 jobs 佇列中,只能有一個 MutationObserver 任務,第二次建立時,前一個 MutationObserver 任務沒有執行,顧不再建立。

相關文章