长任务监听必须用 longtask,浏览器仅识别该 entry type;tbt 需手动计算每个 duration > 50ms 的 longtask 条目超出部分之和;上报须节流,避免监控自身加重性能负担。

长任务监听必须用 longtask,不是 long-task
浏览器只认 longtask 这个 entry type,写成 long-task 或其他变体,observe() 会静默失败,什么也不报——连控制台 warning 都没有。Chrome 98+ 支持 attribution 字段,但前提是开启 entryTypes: ['longtask'] 且页面未被 iframe 沙箱限制。
实际代码里最容易漏掉的是运行环境判断:
- 必须先检查
'PerformanceObserver' in window,IE 和旧版 Safari 不支持 -
observe()调用前要确保页面已进入稳定状态(比如在DOMContentLoaded后),否则可能错过首屏关键长任务 - 不要在模块热更新(HMR)环境中反复新建 observer,会导致重复监听和内存泄漏
TBT 不是直接上报的指标,得自己算
Total Blocking Time 是一个派生值,浏览器不提供现成的 API 返回 TBT 总和。你得手动对每个 longtask 条目做减法:只要 entry.duration > 50,就累加 entry.duration - 50。注意单位全是毫秒,别混淆。
常见误算方式:
- 把所有
duration直接相加 → 错,TBT 只统计「超出 50ms 的那部分」 - 只在 FCP 之后开始收集 → 对,但 FCP 时间点需用
PerformanceObserver监听paint类型并过滤name === 'first-contentful-paint',不能靠performance.timing(已废弃) - 忽略 TTI 边界 → TBT 统计截止到可交互时间(TTI),而 TTI 需通过 long task 密度反推(连续 5 秒无长任务 + 主线程空闲),不能硬设固定时间戳
生产环境上报必须节流,否则监控本身成性能瓶颈
每出现一个长任务就立刻 navigator.sendBeacon(),在高频交互页(比如翻译机实时语音界面)可能一秒触发十几次上报,反而拖慢主线程。实测发现,未节流的监控脚本自身能贡献 10~20ms 的额外 TBT。
推荐做法:
- 用数组缓存条目,每 30 秒 flush 一次,或每满 10 条就发一次
- 上报前过滤掉
duration 的条目(50~60ms 的“擦边”任务对用户体验影响极小,且噪声大) - 带上上下文字段:
page、userAction(如点击/滚动/语音唤醒)、deviceMemory,方便后续归因 - 避免在
requestIdleCallback里上报——它本身可能被调度延迟,导致数据丢失
定位卡顿根源时,attribution 字段远比 name 有用
name 字段在主文档中常为 'self',几乎没信息量;而 Chrome 98+ 的 attribution 是个数组,包含 containerType、containerSrc、scriptURL 等,能直接指向具体 script 文件或 iframe 地址。
但要注意限制条件:
-
attribution在跨域 iframe 或 worker 中不可用 - 需要页面启用
Permissions-Policy: interest-cohort=()(不影响功能,但部分合规策略会禁用) - 低端 Android WebView 基本不返回该字段,得 fallback 到按
startTime对齐用户操作日志来人工交叉分析
真正难的不是采集,是区分「偶发长任务」和「稳定复现的阻塞点」——后者往往藏在第三方 SDK 的初始化逻辑或某个被频繁调用的渲染函数里,光看单次 duration 没用,得结合频率、堆栈、设备分组做聚合分析。










