feng xiaohan

事件循环

JS 的代码分为同步任务和异步任务:

同步任务:在主线程上立即执行的任务,按照代码的顺序依次执行;
异步任务:不进入主线程,进入任务队列。只有任务队列通知主线程执行某个异步任务,该异步任务才会进入主线程

异步任务又分为宏任务和微任务,任务队列又分为宏任务队列微任务队列

宏任务:主代码块(script)、setTimeout、setInterval、setImmediate、I/O、UI rendering,requestAnimationFrame 等;
微任务:Promise.then(async)、MutationObserver、process.nextTick、Object.observe 等;

注意:为方便总结,我将同步任务(主代码块 script)归结到宏任务中,这样就可以以宏任务开启事件循环。

  • 执行一个宏任务(初始是 script,而后是从任务队列中获取);

    script 的执行本身是一个宏任务,是事件循环的第一个任务。

  • 执行过程中如果遇到微任务,就将它添加到微任务队列中,遇到宏任务就将其放入宏任务队列中;
  • 当前宏任务执行完毕后,立即依次执行当前微任务队列中所有的微任务;
  • 在执行微任务时,如果遇到了宏任务,会将其放入宏任务队列中,并继续执行微任务队列中的微任务;
  • 执行完微任务后,执行 UI 渲染,再执行下一个宏任务,重复以上过程。

此为 js 的事件循环机制。以宏任务开始一个事件循环,当所有的微任务执行完毕后(包括执行时微任务时又加入的微任务),再去执行下一个宏任务。

初始示例:

  • 主线程:(script:第一个宏任务,同步执行)
  • 宏任务:script(给主线程)
  • 微任务:null
console.log("script-open");
console.log("1");
setTimeout(() => {
  console.log("9-macro");
  Promise.resolve("10").then((res) => {
    console.log(res);
  });
}, 0);
new Promise((resolve, reject) => {
  console.log("2");
  resolve("4");
})
  .then((res) => {
    console.log(res);
    setTimeout(() => {
      console.log("11-macro");
    }, 0);
    return Promise.resolve("7");
  })
  .then((res) => {
    console.log(res);

    return Promise.resolve("8");
  })
  .then((res) => {
    console.log(res);
  });

new Promise((resolve, reject) => {
  console.log("3");
  resolve("5");
}).then((res) => {
  console.log(res);
});

const el = document.querySelector("#app");
const observer = new MutationObserver((mutationList, observer) => {
  console.log("6-observer", mutationList);
});
observer.observe(el, { subtree: true, childList: true });

// 1 2 3 4 5 6-observer 7 8 9-macro 10 11-macro

Vue 与事件循环

Vue 中的一些语法和钩子函数与事件循环的关系。

  • 创建阶段(setup、create):同步任务(script);
  • onMounted:第一个微任务;
  • nextTick:下一轮事件循环(下一个 tick)的微任务队首;

    例如:如果和 onMounted 在同一层,则在 onMounted 后的一个事件循环的队首。

  • watchEffect:同步任务(script);