JavaScript事件循环

风信子 发布于 21 天前 62 次阅读


概述

下面这两段代码,它们的输出顺序均为1 3 2:

console.log(1)
setTimeout(() => {
  console.log(2)
}, 0)
console.log(3)
console.log(1)
setTimeout(() => {
  console.log(2)
}, 1000)
console.log(3)

JavaScritpt引擎是单线程的,为了避免代码阻塞,将代码(任务)划分了为异步任务同步任务

  • 同步任务直接放在执行栈(Call Stack)中,由JavaScript引擎执行。

  • 异步任务交给宿主环境(Node.js/浏览器),时间成熟后送入任务队列。

    例如:第二段代码里,计时器会在1000ms后将() => {console.log(2)} 这个回调函数送入任务队列

  • 执行栈执行完毕后,会查看任务队列是否有异步任务,有就压入执行栈中执行,不断循环检测,这个过程就是事件循环Event Loop

其中异步任务又被划分为宏任务( HTML 标准中通常直接称为 Task)与微任务,微任务的优先级更高,JavaScript会优先执行微任务队列(Microtask)里的代码。

微任务的出现是可以保证高优先级异步任务的及时执行,微任务处理的是当前任务逻辑的直接延续,而宏任务处理的是相互独立的、系统级的事件调度

执行顺序

同步代码----微任务-----宏任务,具体而言如下:

  • 执行当前宏任务: 运行主线程上的同步代码
  • 微任务入队: 在运行过程中,遇到 Promise.then 等 API,将对应的回调函数放入微任务队列
  • 当前宏任务结束: 当前宏任务的同步代码执行完毕,调用栈清空
  • 执行微任务: JavaScript 引擎立刻检查微任务队列,并执行刚才入队的微任务代码
  • 执行下一个宏任务: 微任务队列清空后,引擎去宏任务队列中取出下一个宏任务(例如 setTimeout 的回调)推入调用栈执行

ps:宏任务(Task)是事件循环进行调度的基本执行单位。执行同步代码的过程,本身就是宏任务执行过程的一部分

所有微任务都是由宏任务产生的


示例如下:

console.log('1. 当前宏任务同步代码开始');

// 注册微任务
Promise.resolve().then(() => {
  console.log('3. 微任务代码执行');
});

// 注册下一个宏任务
setTimeout(() => {
  console.log('4. 下一个宏任务代码执行');
}, 0);

console.log('2. 当前宏任务同步代码结束');

常见的异步任务

  1. 宏任务
  • <Script>整体代码块
  • setTimeout(): 延时定时器
  • setInterval(): 周期定时器
  • DOM 事件回调:用户的click、keydown、mousemove等交互事件触发的回调函数
  • 网络请求与 I/O 操作: 例如 Ajax / Fetch 的响应回调,或者 Node.js 中的文件读写回调
  1. 微任务
  • Promise的then、catch、finally。(promise本身是同步的)

    • process.nextTick:Node.js 环境独有的微任务,优先级高于普通的微任务
  • queueMicrotask():专门用于将回调函数直接推入微任务队列的全局标准方法(浏览器 / Node.js)

  • MutationObserver:用于监听 DOM 树结构或属性变化的 API(浏览器)

练习

console.log(1)
setTimeout(()=>{
  console.log(2)
})
Promise.resolve().then(()=>{
  console.log(3)
})
console.log(4)

这段代码的输出顺序是1、4 、2、3