配了三年前端,今天才用 Chrome 测出来自己的代码在主线程上吃了多少毫秒——这件事以前只能靠猜
主线程是浏览器里最忙的那条线——JS 执行、事件处理、样式计算、布局、绘制,全挤在上面。60Hz 显示器每 16.6ms 要产出一帧,浏览器自己还要吃 4~6ms,留下给代码的只有约 10ms。用户能感知延迟的阈值是 100ms,而超过 50ms 的任务就已经被浏览器标记为”长任务”(Long Task),会直接阻断输入响应。
问题是:你的代码每次占用了主线程多少毫秒,大多数人根本不知道,只能靠页面”卡不卡”来猜。
这件事以前真的只能靠猜
Chrome DevTools 早就有 Performance 面板,但很长一段时间里它只是一条火焰图——黄色块堆在一起,你知道有 JS 在跑,但哪个函数吃了多少毫秒、跟哪个用户操作对得上,并不容易快速说清楚。
这个状况在 2024~2026 年发生了变化。Chrome 持续给 DevTools 加功能,现在 Performance 面板配合几个新 API,已经能把主线程时间精确归因到具体的文件和函数。
Chrome DevTools Performance 面板:主线程时间现在看得见
打开 F12,切到 Performance 标签页,点录制然后做操作(点击、滚动、输入),停止后看 Main 线程轨道:
- 红色三角标记:超过 50ms 的长任务,浏览器主动标红
- 黄色块宽度:JS 执行时长,悬停能看到具体函数名和耗时(如
handleSearch(): 42.6ms) - Bottom-Up 视图:按 Self Time 排序,直接定位吃时间最多的函数本身,排除调用栈包装干扰
- Call Tree 视图:从事件处理函数一路往下追,找到最终那个烧 CPU 的函数
Chrome 154 之后,Performance 面板还加入了 Frames 轨道的帧预算标记线,每 16.67ms 一条,让你一眼看出哪个任务超了帧预算、掉了多少帧。
用 Long Tasks API 把归因写进代码里
Performance 面板适合调试,但要在生产环境持续监控,需要这个 API:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn(`Long Task: ${entry.duration.toFixed(1)}ms`, {
attribution: entry.attribution?.[0]?.name || 'unknown',
startTime: entry.startTime,
});
}
});
observer.observe({ type: 'longtask', buffered: true });
在 DevTools 控制台跑一下,或者写进监控脚本,你就能拿到所有超 50ms 任务的精确耗时和来源脚本。配合 performance.mark() 和 performance.measure(),可以给自己的代码段打标记:
performance.mark('parse-start');
const data = JSON.parse(hugePayload);
performance.mark('parse-end');
performance.measure('json-parse', 'parse-start', 'parse-end');
这样在 Performance 面板里就能看到自定义测量段的耗时,不依赖函数边界。
实测数据:你的代码可能比你想象的贵得多
kciter.so 对 12,000 个真实站点做了测量(Chrome 119、Firefox 122、Safari 17):
- 超过 50ms 的长任务,平均占页面总加载时长的 38%
- 每个页面平均产生 1.7 秒的用户感知延迟
- 42% 的主线程阻塞来自第三方脚本,广告密集型站点这个数字升到 61%
换算一下:Chrome 每天 23 亿活跃用户,如果每个人每天经历 1.7 秒主线程阻塞,加起来每天浪费的用户注意力超过 400 万小时。
这个数字不一定精确,但它说了一件事:主线程不是你代码跑得快不快的问题,而是用户有没有在等你页面响应的生死问题。
怎么用 Chrome 测出自己的数据
步骤很简单:
- 打开目标页面,F12 进 DevTools,切到 Performance
- 点录制,执行你怀疑有性能问题的操作(不要录太久,3~5 秒足够)
- 停止,看 Main 线程轨道:红三角标记的全是长任务
- 点开看 Call Tree,找到 Self Time 最长的那个函数
- 对着函数名去代码里搜,通常能找到:大量循环、复杂 DOM 操作、未批量的 DOM 读写交替
判断标准:如果一个任务的 Blocking Time(超过 50ms 的部分)加起来超过 200ms,INP 分数大概率已经在 Needs Improvement 区间。
怎么修比怎么测更吃经验,但测出来就成功了一半
知道哪个函数慢了,修复思路就清晰了:
- 分片:
scheduler.yield()(Chrome 115+)让出主线程让浏览器先处理输入,再继续跑;setTimeout(fn, 0) 是通用降级方案 - 迁移到 Worker:
postMessage把数据传给 Worker 处理,结果回来再写 DOM,主线程完全不停 - 批量 DOM 操作:把读操作全放前面、写操作全放后面,避免强制回流(reflow)
- 代码分割:首屏不需要的模块用动态
import()懒加载
但所有这些优化的前提是:你得先知道哪个函数慢了,而不是靠感觉猜。
现在 Chrome 已经把这个能力做进去了,你欠自己的页面一次 Performance 面板录制。
落地方下一步:
打开你负责的线上页面,F12 → Performance → 录制 3 秒 → 看 Main 线程有没有红三角。有一个的话,这篇文章里的 PerformanceObserver 代码直接复制进控制台跑一次,把归因截图保存下来,这就是你接下来要修的第一个函数。
评论区
登录后可评论。