事件循环:
- 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特有,优先级高)
- 宏任务: 独立,颗粒度大,每次事件循环迭代通常就处理一个宏任务
事件循环的单轮流程:
- 执行同步代码,直到调用栈清空。在这个过程中遇到的异步任务(宏任务、微任务)被注册并放到相应的队列里。
- 执行所有微任务
- 清空微任务队列
- 执行一个宏任务
- 从宏任务队列取第一个执行
- 重复上述过程 🔁
关键注意事项:
- 同步代码永远优先
- 微任务优先于宏任务
- node.js环境的差异
- 宏任务之前会有微任务清空及潜在的渲染
核心总结:
- 事件循环是js运行时环境中实现异步操作的核心机制
- 任务分宏任务和微任务
- 执行顺序:同步 -> 所有微任务 -> 一个宏任务(循环)