EventLoop 异步更新
约 432 字大约 1 分钟
2024-08-14
浏览器 Event Loop
浏览器 Event Loop 负责协调 JavaScript 执行、渲染和合成线程的工作。
执行流程
1. 执行同步任务
2. 执行所有微任务(Microtasks)
3. 渲染更新(Rendering)
4. 执行下一个宏任务(Macrotask)任务队列
| 类型 | 示例 | 执行时机 |
|---|---|---|
| 同步任务 | 普通 JS 代码 | 立即执行 |
| 微任务 | Promise.then, MutationObserver, queueMicrotask | 当前宏任务结束后、下一个渲染前 |
| 宏任务 | setTimeout, setInterval, I/O, UI Rendering | 下一轮事件循环 |
异步更新策略
1. 批量更新(Batching)
将多次状态更新合并为一次渲染:
// 反面示例:多次触发更新
element.style.width = '100px';
element.style.height = '100px'; // 可能触发两次回流
// 优化:合并更新
element.style.cssText = 'width: 100px; height: 100px;';2. 使用 requestAnimationFrame
在下一帧渲染前执行更新:
// 在动画中使用
function animate() {
updatePhysics();
element.style.transform = `translateX(${x}px)`;
requestAnimationFrame(animate);
}3. 使用 requestIdleCallback
在浏览器空闲时执行低优先级任务:
requestIdleCallback((deadline) => {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
processTask(tasks.shift());
}
}, { timeout: 2000 });4. 微任务优化
// 使用 Promise.resolve().then() 延迟执行
function deferUpdate(fn) {
return Promise.resolve().then(fn);
}
// 批量处理微任务
async function batchProcess(items) {
const batchSize = 100;
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
await Promise.all(batch.map(item => process(item)));
// 让出主线程,允许其他任务执行
await new Promise(resolve => setTimeout(resolve, 0));
}
}实践建议
- 避免同步模式下的多次更新:合并 DOM 操作
- 长任务拆分:使用
requestIdleCallback或 Web Worker - 合理使用微任务:微任务中避免执行耗时操作
- 动画优化:使用
transform和opacity实现 GPU 加速
参考文章:
