JavaScript 在一个执行线程上运行用户代码,但浏览器还提供网络、定时器、渲染等能力。异步任务完成后会进入队列,由事件循环在合适的时机交给 JavaScript 执行。
一次循环发生什么
1 | 执行当前同步代码 |
常见 Task 包括点击事件、定时器和网络事件;Promise 回调和 queueMicrotask 属于 Microtask。Microtask 持续递归加入自己,也可能让页面长时间无法渲染。
1 | console.log('A') |
输出通常是 A D C B:同步代码先执行,Promise Microtask 先于下一个定时器 Task。
async/await 没有阻塞整个浏览器
1 | async function loadUser() { |
await 会暂停当前异步函数,控制权返回事件循环;网络由浏览器处理,结果就绪后再安排后续代码。它看起来像同步流程,但错误、取消和并发仍要显式处理。
一次 Vue 交互
1 | 用户点击 |
DOM 更新通常不是在赋值语句后立即完成;确实需要等待更新结果时使用 nextTick,不要到处用定时器猜测渲染时机。
状态应放在哪里
| 状态 | 合适位置 |
|---|---|
| 输入框、弹窗、局部选中 | 组件本地 |
| 页面筛选和分页 | 页面或 URL |
| 用户身份、主题等跨页面状态 | 全局 Store |
| 接口数据、缓存和重新验证 | 请求缓存层 |
| 必须跨刷新保留的数据 | 服务端或受控本地存储 |
不要把所有接口结果复制进全局 Store,否则容易出现服务端数据与前端副本不同步。
常见性能问题
- 长任务阻塞事件循环,导致点击无响应。
- 大列表一次渲染,造成布局和绘制开销。
- Watcher 或 Effect 形成循环更新。
- 高频输入、滚动事件触发过多计算和请求。
- 在主线程进行大图处理或复杂序列化。
优先通过 Performance 工具找到长任务,再考虑拆分任务、虚拟列表、Web Worker、节流或减少响应式范围。