面试官:说说你对事件循环的理解 ?

58 阅读2分钟

是什么

JS 主线程只能一次干一件事,遇到要等的活(比如等网络、等定时器),总不能傻等着啥也不干。

事件循环就像个 “记事儿的”:先让主线程把不用等的活干完,再把 “等完要做的后续事” 排好队,等主线程一闲,就按顺序递给他干,这样既不耽误事,也不卡壳。

javascript 中,所有的任务可以分成两种

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等

宏任务与微任务

微任务

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前 常见的微任务有:

  • Promise.then
  • MutaionObserver
  • Object.observe(已废弃;Proxy 对象替代)
  • process.nextTick(Node.js)

宏任务

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务有:

  • script (可以理解为外层同步代码)
  • setTimeout/setInterval
  • UI rendering/UI事件
  • postMessage、MessageChannel
  • setImmediate、I/O(Node.js)

样例分析

console.log('start');

setTimeout(() => {
  console.log('timeout1');
  new Promise((resolve) => {
    resolve();
  }).then(() => {
    console.log('then1');
  });
}, 0);

new Promise((resolve) => {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('then2');
  setTimeout(() => {
    console.log('timeout2');
  }, 0);
});
console.log('end');
要求:写出最终打印顺序
解析步骤:
  1. 执行同步任务

    • console.log('start') → 打印 start
    • 遇到 setTimeout(timeout1),宏任务入队;
    • 遇到 new Promise,回调同步执行 → 打印 promise1resolve() 标记微任务;
    • console.log('end') → 打印 end
      同步任务结束,执行栈空。
  2. 清空微任务队列

    • 执行 Promise.then(then2)→ 打印 then2
    • then2 内部有 setTimeout(timeout2),宏任务入队(此时宏任务队列:timeout1、timeout2);
      微任务清空,执行栈空。
  3. 执行第一个宏任务(timeout1)

    • 打印 timeout1
    • 内部 new Promise 的 then 是微任务,入队;
      当前宏任务执行完,立即清空微任务 → 打印 then1
      微任务清空,执行栈空。
  4. 执行第二个宏任务(timeout2)

    • 打印 timeout2
最终结果:start → promise1 → end → then2 → timeout1 → then1 → timeout2

总结

同步任务全执行 → 微任务全清空 → 执行一个宏任务 → 微任务全清空 → 执行下一个宏任务 → 循环直到所有任务完成。