performance.mark() 传入重复 name 会同步抛 SyntaxError;measure 的起止名须已存在 mark;observe 必须显式配置 entryTypes;上报需节流或批处理,且及时 disconnect。

performance.mark() 会抛 SyntaxError?检查 name 是否重复
调用 performance.mark() 时如果传入已存在的 name,浏览器会直接抛 SyntaxError。这不是警告,是同步中断错误,容易导致后续逻辑不执行。
常见场景:Vue/React 组件反复挂载、路由复用组件、或测试中多次运行同一段打点代码。
- 每次调用前先用
performance.getEntriesByName("xxx")检查是否已存在(但注意:该方法返回数组,需判空而非真假值) - 更稳妥的做法是加时间戳或随机后缀,例如
performance.mark(`api-start-${Date.now()}`) - 避免硬编码相同字符串,尤其在可复用的 hook 或工具函数里
measure 的两个 name 参数必须都已 mark 过
performance.measure() 的第二、三个参数(起始和结束标记名)必须对应已成功注册的 mark 条目,否则生成的 measure 条目 duration 为 0,且不会出现在 getEntriesByType("measure") 结果中。
典型错误现象:控制台没报错,但 observer 回调收不到该条目,或 getEntriesByType("measure") 返回空数组。
- 确保
mark在measure之前执行,且未被 GC(实际极少发生,但异步逻辑中容易误序) - 调试时可用
performance.getEntriesByType("mark")立即确认两点是否存在 - 不要依赖
performance.timing类属性(如navigationStart)直接传给measure()—— 它只接受字符串名,不接受时间戳数值
PerformanceObserver.observe() 必须显式声明 entryTypes
即使你监听的是 "measure",也必须在 observe() 调用中传入 { entryTypes: ["measure"] },否则回调永远不会触发。
这是最容易被忽略的配置项。旧文档或 Stack Overflow 示例常省略它,导致“注册了 observer 却没收到数据”的困惑。
- 不能只靠
type: "measure"(那是PerformanceObserver构造函数的选项,已废弃) - 支持多个类型,例如
{ entryTypes: ["measure", "paint", "resource"] },但每种类型需独立处理 - 若之后要监听新类型,需再次调用
observe(),原监听不受影响
上报 measure 数据前务必节流或队列化
PerformanceObserver 回调是同步触发的,且可能高频(比如滚动中连续打点),直接在回调里调用 navigator.sendBeacon() 或 fetch() 容易阻塞主线程,甚至触发浏览器限流。
真实项目中,measure 可能一秒内产生几十条,而网络请求不是设计来这么用的。
- 用
setTimeout(..., 0)或queueMicrotask()把上报推到微任务队列 - 更推荐攒批:维护一个数组,每 1s 或满 10 条统一上报,再清空
- 别在回调里读取
entry.detail后立刻 JSON.stringify —— 若 detail 是大型对象(如整个响应体),序列化本身就有开销 - 记得在组件卸载或页面离开前调用
observer.disconnect(),否则 observer 持有对 entry 的引用,可能阻止 GC
自定义指标真正难的不是打点,而是让 mark 和 measure 在复杂异步、条件分支、错误路径下始终成对出现;以及在 observer 回调里做轻量判断时,不因细节处理(比如深 clone、正则匹配长 name)拖慢渲染帧。










