必须用performanceobserver监听"longtask"类型获取长任务数据,settimeout等无法捕获真实长任务;attribution字段不可靠且实验性;需聚合上报并做兼容性检测。

长任务监控必须用 PerformanceObserver,不能靠 setTimeout 模拟
浏览器只在主线程阻塞超过 50ms 时才触发长任务(Long Task),且仅通过 PerformanceObserver 的 "longtask" 类型暴露。试图用 setTimeout(fn, 0) 或 requestIdleCallback 估算执行耗时,完全无法捕获真实长任务——它们既不感知任务队列实际排队/执行时间,也无法拿到 startTime、duration 和 attribution 等关键字段。
必须注册 observer 并监听 "longtask",否则数据为零:
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log(entry.startTime, entry.duration, entry.attribution);
});
});
observer.observe({ entryTypes: ["longtask"] });
attribution 字段不可靠,别直接当调用栈用
attribution 是实验性字段,Chrome 当前仅在部分场景下填充(如脚本执行、样式计算),且内容是简化后的 TaskAttributionTiming 数组,不是完整堆栈。它可能为空、长度为 0,或只包含顶层 script URL,无法定位到具体函数名或行号。
- 不要依赖
entry.attribution[0]?.container?.src做错误归因 - 若需定位代码,应结合
performance.getEntriesByType("navigation")和资源加载时间交叉比对 - 服务端上报时建议始终携带
entry.name(固定为"self")和entry.entryType(固定为"longtask"),避免解析失败
上报前务必做聚合,否则日志爆炸且无分析价值
单个长任务可能在滚动、动画、首屏渲染等过程中密集触发几十次,每条原始 entry 只有毫秒级精度,直接上报会导致:带宽浪费、存储成本飙升、查询卡顿。必须在内存中做轻量聚合再发。
推荐按「500ms 时间窗口 + 同一调用上下文」粗略分组(无需精确溯源):
const buckets = new Map();
observer.observe({ entryTypes: ["longtask"] });
new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
const bucketKey = Math.floor(entry.startTime / 500); // 每500ms一个桶
const bucket = buckets.get(bucketKey) || { count: 0, total: 0, max: 0 };
bucket.count++;
bucket.total += entry.duration;
bucket.max = Math.max(bucket.max, entry.duration);
buckets.set(bucketKey, bucket);
});
// 每2秒上报一次聚合结果(防抖)
clearTimeout(window._ltReportTimer);
window._ltReportTimer = setTimeout(() => {
const data = Array.from(buckets.values());
navigator.sendBeacon("/log", JSON.stringify({ type: "longtask", data }));
buckets.clear();
}, 2000);
});
- 不推荐按
entry.attribution分组——字段缺失率高,分组键不稳定 - 避免使用
fetch上报:可能被页面卸载中断;navigator.sendBeacon是唯一可靠选择 - 聚合周期不宜过长(>5s),否则丢失短时性能尖峰特征
注意兼容性与降级:Safari 和旧版 Chrome 不支持 "longtask"
PerformanceObserver 本身支持较广(Chrome 64+、Firefox 75+、Safari 15.4+),但 "longtask" 类型在 Safari 中直到 15.4 才启用,且默认关闭;iOS Safari 15.4–16.3 存在已知 bug:偶发不触发回调。
上线前必须加运行时检测:
if ("PerformanceObserver" in window) {
const po = new PerformanceObserver(() => {});
try {
po.observe({ entryTypes: ["longtask"] });
// ✅ 支持,可启用监控
} catch (e) {
// ❌ 不支持,跳过或 fallback 到 FPS 监控
}
}
- 不要用
typeof PerformanceObserver !== "undefined"代替功能检测 - 对不支持环境,可用
requestAnimationFrame差值法估算帧耗时(非长任务等价,仅作兜底参考) - 上报数据中必须标记
browser.version和support.longtask布尔值,便于后续过滤分析
长任务的真正难点不在采集,而在归因——attribution 的缺失让“哪段 JS 导致卡顿”变成概率推测。别把上报当终点,得配合 sourcemap 解析、构建产物体积分析、以及用户操作路径还原,才能逼近根因。










