1. 程式人生 > >Event Loop 事件迴圈

Event Loop 事件迴圈

同步和非同步

首先要明確:

JS是單執行緒語言

也就是說,JS一次只能做一件事情。

cpu處理指令速度非常快,遠比磁碟I/O和網路I/O速度快,所以一些cpu直接執行的任務就成了優先執行主線任務(即同步任務synchronous),然後需要io返回資料的任務就成了等待被執行的任務(即非同步任務asynchronous)

  • 同步任務:在主執行緒上排隊執行的任務,前一個任務執行完畢,才能執行後一個任務;
  • 非同步任務:不進入主執行緒、而進入”任務佇列”(task queue)的任務,只有”任務佇列”通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。

所以:

當要主執行緒任務完成會後,就會去讀取”任務佇列”,這就是JavaScript的執行機制

Microtasks 和 Macrotasks

具體到任務佇列,又分位 microtasks 和 macrotasks

屬於microtasks的任務有:

  • process.nextTick
  • promise
  • Object.observe
  • MutationObserver

屬於macrotasks的任務有:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI渲染

在執行事件時:

  1. 從script(整體程式碼)開始第一次迴圈。執行所有主執行緒的同步函式,遇到非同步函式,分別新增到microtasks和macrotasks任務佇列
  2. 同步函式執行後,開始執行非同步函式中的任務佇列,首先執行所有的micro-task
  3. 所有的micro-task執行完成後,迴圈執行macro-task任務
  4. macro-task任務執行,再次執行micro-task,這樣一直迴圈下去

簡單來件,整體的js程式碼在執行完主執行緒的同步任務,然後有microtask執行microtask,沒有microtask執行下一個macrotask,如此往復迴圈至結束

練習

setTimeout(function() {
  console.log(4)
}, 0);
new Promise(function(resolve) {
  console.log(1)
  for (var i = 0; i < 10000; i++) {
    i == 9999
&& resolve() } console.log(2) }).then(function() { console.log(5) }); console.log(3); // 結果是1 2 3 5 4

再來一個:

console.log('start')
const interval = setInterval(() => {
  console.log('setInterval')
}, 0)
setTimeout(() => {
  console.log('setTimeout 1')
  Promise.resolve().then(() => {
    console.log('promise 3')
  }).then(() => {
    console.log('promise 4')
  }).then(() => {
    setTimeout(() => {
      console.log('setTimeout 2')
      Promise.resolve().then(() => {
        console.log('promise 5')
      }).then(() => {
        console.log('promise 6')
      }).then(() => {
        clearInterval(interval)
      })
    }, 0)
  })
}, 0)
Promise.resolve().then(() => {
  console.log('promise 1')
}).then(() => {
  console.log('promise 2')
})

// 執行順序是
// start
// promise 1
// promise 2
// setInterval
// setTimeout 1
// promise 3
// promise 4
// setInterval
// setTimeout 2
// promise 5
// promise 6

參考