实战录制:用 Performance 面板追踪卡顿根源
说实话,很多同学做内存泄漏排查,第一反应就是打开 Chrome 的 Memory 面板,等着快照比较。但我觉得,真正卡顿的那几秒里,主线程到底在忙什么,才是破案的关键。今天我们就来聊聊 Performance 面板怎么录、怎么看、怎么抓住那些“长任务”。
1. Record 录制主线程活动
先说我自己的习惯。遇到卡顿,我不是直接点 Memory,而是先打开 Performance,点击那个圆点 Record。录的时候,我会让页面重复几次有问题的操作(比如滚动、点击弹窗、切换 tab),然后停录。
看下录制后的界面:
| 录制区域 | 看什么 |
|---|---|
| 顶部时间轴 | 总时长、事件序列、FPS 下探的区间 |
| 主线程火焰图 | 黄色 / 紫色的长条就是任务栈,越宽越耗时 |
| 摘要面板 | 选中某段时间,看函数名、耗时、调用关系 |
| 长任务标记 | 红色对勾或者红色虚线框,就是 Long Task |
小技巧: 录制前先清一下缓存,并且把 CPU 降速到 4x 或 6x。这样卡顿更容易复现,而且录制出来的火焰图更“明显”。我遇到过一个 case,不降速根本看不出来,降速后才发现有个定时器在疯狂创建 DOM 节点。
2. 火焰图——卡顿的显微镜
火焰图长什么样?说白了就是一个堆叠的彩色条形图。每个方块代表一个函数调用,方块宽度代表执行时间。越靠近底部的是根调用,越往上是子函数。
我记得最有用的一条经验:从火焰图的顶部往下看。如果某个宽条一直不变,颜色还特别亮(比如紫色代表渲染),那就可疑。我曾经碰到一个页面,每次滚动都会触发 updateWidget,这个函数调了近 200ms,而它内部竟然在重新遍历一个 5000 项的数组,每次都新建对象。这就是泄漏的前兆——对象越来越多,遍历越来越慢。
解读步骤
- 找到最宽的方块——时间消耗大户。
- 看调用栈——点它,下面会显示从入口到该函数的完整路径。
- 留意重复模式——同一个宽条反复出现,说明可能有人在循环里做重计算。
- 检查
GC事件——火焰图里灰白色的长条可能是垃圾回收,如果它频繁出现且持续时间超过 50ms,说明内存碎片化严重。
重点: 别被“长任务”字样吓到。Chrome 把任何超过 50ms 的任务标为 Long Task,但 40ms 的任务也很危险。你想想看,屏幕刷新率 60Hz,一帧只有 16.6ms,一个任务占了 40ms 就意味着这个帧必掉。
3. 长任务识别——抓出“罪魁祸首”
在 Performance 录制结果里,长任务通常会用红色标记(一个红色的对勾或者红色半透明方块)。你可以直接点击那个标记,右侧会显示它的调用栈和持续时间。
我曾经在排查一个复杂表格组件时,发现有个长任务持续了 350ms。点进去一看,堆栈深处有个 scrollHandler,里面调用了 getBoundingClientRect 和 offsetHeight——这两个都是强制回流。而且因为上一次滚动后 DOM 结构变了,样式计算被反复触发。这就是典型的“布局抖动 + 内存泄漏”组合拳。
长任务常见类型
| 类型 | 典型耗时 | 可能原因 |
|---|---|---|
| JS 执行 | > 80ms | 大循环、字符串拼接、不必要的深拷贝 |
| 样式计算 | > 20ms | 大量选择器匹配、层叠样式表过长 |
| 布局 | > 30ms | 频繁读写 offsetTop, scrollLeft |
| 垃圾回收 | > 30ms | 大量短期对象、闭包引用未释放 |
避坑指南: 我以前吃过一个亏:Performance 录了 10 秒,结果火焰图全是灰白色的 GC 块,但我只看红色长任务标记,差点漏掉。其实 GC 本身就是一种任务,如果它频繁并且超 50ms,你要怀疑是不是有内存泄漏导致 GC 耗时越来越长。所以,不要只看红色标记,还要看甘特图里的所有“长”条。
知识结构图:从录制到定位的完整流程
下面的 SVG 图总结了整个排查路线:
实战小贴士
- 录制时长不要太长——我建议每次 5-8 秒,太长难以分析,太短可能捕捉不到卡顿。
- 配合控制台日志——在可疑的代码位置加
console.time和console.timeEnd,这样 Performance 里能看到自定义标记,更容易对应。 - 导出录制结果——点击右上角的“保存”,导出 .json 文件。发给同事或事后复盘很方便。
一句话总结: Record 录的是时间线,火焰图给的是调用栈,长任务是“报警器”。三者结合,你就能准确知道——哪一段代码、什么时候、花了多少时间、为什么卡。这是内存泄漏排查中最直观的一步。