共计 1933 个字符,预计需要花费 5 分钟才能阅读完成。
JavaScript 单线程与 Event Loop 基础
浏览器中 JavaScript 采用单线程模型,这意味着所有任务需要排队执行。Event Loop(事件循环)是协调这些任务的核心机制,其工作流程可简化为:

- 从宏任务队列(如 script 整体代码)中取出一个任务执行
- 执行过程中产生的微任务加入微任务队列
- 当前宏任务执行完毕后,立即清空微任务队列
- 必要时进行 UI 渲染
- 开始下一轮循环
宏任务 vs 微任务
- 宏任务 :
- 包含:script 整体代码、setTimeout、setInterval、I/ O 操作、UI 渲染
-
特点:每次 Event Loop 只执行一个宏任务
-
微任务 :
- 包含:Promise.then、MutationObserver、queueMicrotask
- 特点:必须在本轮事件循环中立即执行完毕
常见异步 API 执行时序分析
setTimeout 与 Promise 对比
console.log('script start');
setTimeout(() => {console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {console.log('promise1');
}).then(() => {console.log('promise2');
});
console.log('script end');
执行顺序分析:
- 主脚本作为第一个宏任务执行
- setTimeout 回调进入宏任务队列
- Promise.then 回调进入微任务队列
- 主脚本执行完毕,开始处理微任务队列
- 最后执行下一个宏任务(setTimeout)
输出结果:
script start
script end
promise1
promise2
setTimeout
requestAnimationFrame 的特殊性
requestAnimationFrame(() => {console.log('rAF');
});
Promise.resolve().then(() => {console.log('microtask');
});
setTimeout(() => {console.log('setTimeout');
}, 0);
执行特点:
– requestAnimationFrame 在渲染前执行
– 执行顺序:微任务 → requestAnimationFrame → 渲染 → 宏任务
异步调度导致的性能问题
案例 1:长任务阻塞
// 错误示例:密集微任务导致页面冻结
function recursiveMicrotask() {Promise.resolve().then(recursiveMicrotask);
}
案例 2:定时器累积
// 错误示例:未清理的 setInterval 导致内存泄漏
let count = 0;
const timer = setInterval(() => {if(count++ > 10) return;
// 业务逻辑
}, 100);
// 忘记调用 clearInterval(timer)
生产环境最佳实践
- 任务拆分原则 :
- 将长任务拆分为多个短任务(<50ms)
-
使用 setTimeout(fn, 0) 或 queueMicrotask 分片执行
-
优先使用微任务 :
- 对时效性要求高的操作使用 Promise 而非 setTimeout
-
注意避免微任务嵌套过深
-
动画优化方案 :
- 使用 requestAnimationFrame 替代 setInterval
-
CSS 动画优先考虑 transform 和 opacity 属性
-
资源清理机制 :
- 组件销毁时清除所有定时器和事件监听
-
使用 AbortController 取消未完成的 fetch 请求
-
错误边界处理 :
- Promise 链中添加 catch 处理
- 对关键异步操作实现重试机制
进阶思考:异步任务调度系统设计
考虑以下需求场景:
– 需要同时处理实时数据更新和批量计算任务
– 用户交互事件需要立即响应
– 后台同步任务不能阻塞主线程
可以尝试设计包含这些特性的调度系统:
1. 任务优先级分级(紧急 / 高 / 中 / 低)
2. 基于 MessageChannel 的任务分片
3. 空闲期任务调度(requestIdleCallback)
4. 任务取消和暂停能力
class TaskScheduler {constructor() {this.pendingTasks = new Map();
this.channel = new MessageChannel();}
addTask(task, priority) {// 实现优先级队列}
run() {// 使用 requestIdleCallback 调度低优先级任务}
}
理解 Chrome 的异步调用机制,能帮助开发者编写出更高效、更可靠的 Web 应用。建议读者通过 Chrome DevTools 的 Performance 面板实际观察不同异步任务的执行时序,这将大大加深对 Event Loop 的理解。
正文完
发表至: 前端开发
近一天内
