页面卡顿(不流畅/lag)与“加载慢”的排查思路完全不同。加载慢关注网络与资源,卡顿关注主线程与渲染帧率。
从浏览器渲染原理出发,系统性地定位并解决“掉帧”问题。
1. 核心原理:何为“卡顿”?
浏览器刷新率通常为60fps,即每帧约16.6ms。如果JS任务或渲染任务在该时间内无法完成,帧就会被“卡住”,用户感知为卡顿。 所以排查核心只有一个:找到占据主线程超过16.6ms的“长任务(Long Task)”。
2. 排查工具箱(Performance)
打开DevTools → Performance面板,点击录制,复现卡顿场景。
- 看火焰图(Main):大片的深黄色/紫色块就是罪魁祸首。紫色代表布局重排(Reflow),黄色代表JS执行。
- 看总结栏(Summary):按总耗时排序。如果Layout占比>20%,说明布局抖动;如果Scripting占比>50%,说明JS计算过多。
- 看Bottom-Up:倒排查哪个函数调用了最多次、耗时最长。
3. 四类常见卡顿及解法
类型一:高频率触发引发的卡顿(滚轮/输入)
- 特征:滚动页面、打字时卡顿明显。
- 原因:
scroll/input事件回调执行太频繁,或DOM操作太重。 - 解法:防抖(Debounce)或节流(Throttle)。动画相关用
requestAnimationFrame包裹,保证与屏幕刷新同步。
类型二:强制同步布局/布局抖动(Layout Thrashing)
- 特征:火焰图中出现紫色的紫色块(Layout)堆积,且在JS执行紫色之间反复出现紫色块。
- 代码体现:在循环里先读样式
div.offsetHeight,又立即改样式div.style.height = x + 'px'。浏览器被迫在每次循环中立即重新计算样式,而非批量处理。 - 解法:读写分离。先将所有读操作做完,最后统一做写操作。或者用
FastDom这种批量更新库。
类型三:JS计算/渲染密集(大列表、Canvas)
- 特征:脚本执行时间很长(>100ms),火焰图里一大片黄色块。
- 原因:同时渲染1000个复杂DOM节点;或在循环里做复杂计算、拼接超大字符串。
- 解法:
- 虚拟滚动:只渲染视口内可见项。
- 分片执行:将长任务拆解,用
setTimeout或requestIdleCallback在空闲时间执行。 - Web Worker:将纯计算任务(如数据排序、解析)放到Worker线程。
类型四:内存泄漏导致页面越来越卡
- 特征:刚开始不卡,操作久了越点越慢,甚至页面崩溃。
- 排查:Performance录制勾选Memory,看JS堆大小曲线是否只升不降(锯齿状才是正常GC)。
- 常见:未清理的事件监听、闭包引用大对象、DOM引用残留、定时器未清除。
- 解法:在
beforeDestroy或useEffect的清理函数中,手动removeEventListener、clearInterval、解绑Observer。