buffered: true 是必须加的开关,否则 PerformanceObserver 无法捕获页面加载早期的关键性能条目(如 FCP、CLS、LCP 等),因浏览器不会补发已发生的记录。

buffered: true 不是可选,是必须加的开关
不加 buffered: true,PerformanceObserver 就只能捕获调用 observe() 之后新产生的性能条目,页面加载早期的关键事件(比如首屏资源、FCP、layout-shift)全都会丢。浏览器不会为你“补发”已经过去的记录——除非你明确告诉它“把缓冲区里还活着的历史条目也推给我”。
这个缓冲区不是无限的:只保留当前页面会话中尚未被垃圾回收的条目,通常覆盖从 navigationStart 到当前时刻,但不跨 SPA 路由或页面跳转。
-
buffered: true必须写在observe()的配置对象里,不能只写在构造函数参数中 - 它对所有支持的
entryTypes都生效,但不同条目类型的历史可回溯能力有差异(比如navigation条目始终只有一条,而resource可能有几十条) - 某些低内存设备或后台标签页中,浏览器可能提前 GC 掉部分历史条目,所以不能假设一定能拿到全部
哪些 entryTypes 真正依赖 buffered 才能拿到首屏数据
不是所有类型都“需要” buffered,但以下几类若不加,大概率拿不到关键首屏指标:
-
'paint':FCP、FP 这类绘制时间点发生在 DOM 解析早期,脚本晚于DOMContentLoaded注册 observer 就必然漏掉 -
'layout-shift':CLS 计算依赖首屏偏移,而首屏广告/图片懒加载常触发早期 layout-shift,不加 buffered 就无法还原真实 CLS -
'first-input':用户首次点击可能发生在 JS 加载完成前,没 buffered 就等于放弃监控首交体验 -
'largest-contentful-paint':LCP 元素往往在首屏渲染阶段就已确定,延迟注册 observer 会导致 LCP 条目丢失
而像 'navigation' 或 'measure' 这类条目,前者只有一条且生命周期长,后者由开发者主动打点,对 buffered 依赖较低。
buffered 导致的常见误判和调试盲区
开了 buffered 后,回调里一次可能收到十几甚至上百条历史条目,容易引发两个问题:
把任意文章或文档内容转换成精美的全屏翻页 HTML 演示文件(幻灯片风格)。当用户发来一篇文章、文档链接、Markdown 内容、或粘贴的文本,并且希望生成演示用的 HTML 时触发。支持任意内容来源:iWiki 链接、KM 链接、本地文件路径、直接粘贴的文本。生成的 HTML 文件保存到本地工作区,并通过企微直接发送给用户。触发词示例:帮我把这篇文章做成PPT、转成演示文档、生成HTML幻灯片、做个演示、转成可以演示的格式、article to deck、make a slide。
- 重复上报:如果 observer 在 SPA 中未销毁,每次路由切换后又重新
observe(),历史条目会被反复推送(因为缓冲区未清空),导致同一资源被上报多次 - 时间戳错乱:
entry.startTime是相对performance.timeOrigin的绝对时间,但回调触发时间远晚于条目产生时间,直接用Date.now()做“上报延迟”计算会严重失真 - 内存泄漏风险:长期存活的 observer + 大量历史条目(尤其
resource类型)可能拖慢 GC,建议在 SPA 页面卸载时调用observer.disconnect()
验证是否真的拿到了历史条目?打印 list.getEntries().length 并检查最早一条的 entry.startTime 是否明显早于当前脚本执行时间。
buffered 与 entryTypes 组合的实际写法
正确写法必须把 buffered: true 放进 observe() 参数,且和 entryTypes 同级:
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log(entry.entryType, entry.name, entry.startTime);
});
});
// ✅ 正确:buffered 在 observe() 里
observer.observe({ entryTypes: ['paint', 'resource'], buffered: true });
// ❌ 错误:buffered 写在构造函数里无效
// const observer = new PerformanceObserver(callback, { buffered: true });
注意:entryTypes 是数组,buffered 是布尔值,两者不能合并进一个对象传给构造函数——这是最常踩的语法坑。
真正难的不是加 buffered,而是判断哪些条目该留、哪些该过滤、哪些要聚合后再上报。历史数据越多,越需要业务语义兜底,否则只是把浏览器的原始日志搬上服务器而已。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










