事件循环

8 阅读2分钟

事件循环:

  • js执行异步的机制
  • 协调浏览器/node.js环境
  • 处理任务队列:宏任务,微任务
  • 在主线程空闲时执行任务
  • 实现非阻塞与响应性

为什么需要事件循环:

  • js是单线程
  • 单线程的阻塞问题
  • 实现非阻塞I/O:网络请求,文件读写
  • 保证用户界面响应性:DOM更新,事件监听
  • 支持异步编程模型:Promise/async/await, setTimeout/fetch

事件循环工作原理:

  • 核心组成
    • 调用栈(Call Stack):同步代码执行的地方
    • 堆(Heap):变量和对象存储在内存中的地址
    • 任务队列 (Task Queues):存储等待执行的异步任务回调
    • 事件循环本身:是一个持续运行的进程,负责监控调用栈和任务队列,并在合适的时机将队列中的任务移到队列中执行
    • 任务分类:
      • 宏任务: 独立,颗粒度大,每次事件循环迭代通常就处理一个宏任务
        • setTimeOut():本身是异步的,它的回调函数并不会立即执行,而是被js运行时注册,在指定延迟后将回调函数放到宏任务队列里。即使延迟被设置为0,依然被视为一个宏任务,需要等待事件循环来调度。
        • setInterval()
        • DOM事件: 点击,键盘输入回调
        • I/O操作完成回调: 文件读取完毕,网络请求返回响应需要处理的回调
        • UI渲染(绘制):在连续的js执行期间,浏览器有机会去更新页面
        • setImmediate() (Node.js)
      • 微任务:通常和当前执行的任务紧密相关,需要尽快执行。和宏任务不同,在每次事件循环迭代的微任务阶段会执行所有可用的微任务,直到微任务队列清空
        • promise.then()/.catch()/.finally(): 当promise状态发生变化,变为fulfilled、rejected,这些回调被放入微任务队列
        • queueMicrotask()
        • MutationObserver回调:观察dom树的变化
        • process.nextTick()(node.js特有,优先级高)

事件循环的单轮流程:

  1. 执行同步代码,直到调用栈清空。在这个过程中遇到的异步任务(宏任务、微任务)被注册并放到相应的队列里。
  2. 执行所有微任务
    • 清空微任务队列
  3. 执行一个宏任务
    • 从宏任务队列取第一个执行
  4. 重复上述过程 🔁

关键注意事项:

  • 同步代码永远优先
  • 微任务优先于宏任务
  • node.js环境的差异
  • 宏任务之前会有微任务清空及潜在的渲染

核心总结:

  • 事件循环是js运行时环境中实现异步操作的核心机制
  • 任务分宏任务和微任务
  • 执行顺序:同步 -> 所有微任务 -> 一个宏任务(循环)