performance.mark需配合measure才能准确计时;仅mark不measure则无耗时数据,且mark名须唯一、区分大小写;高频打点需清理,上报应监听measure事件后立即sendBeacon。
performance.mark 不是万能计时器,它只打点、不计算;真正要得出耗时,必须配合 performance.measure 或手动用 performance.getEntriesByName 拿时间戳相减。直接调用 mark 却不 measure,等于埋了点却没读数。
业务中常见错误是只在关键位置调用 performance.mark('start') 和 performance.mark('end'),但漏掉 performance.measure 这一环——结果 getEntriesByType('measure') 里查不到任何数据。
performance.measure('name', 'start', 'end'),其中两个字符串参数要和 mark 名完全一致(区分大小写、空格)performance.mark('api_start')),measure 默认取最后一次 mark 的时间,容易误判;建议加唯一后缀,如 'api_start_123'
performance.clearMarks() 或 performance.clearMeasures()
看似可行:const start = performance.getEntriesByName('start')[0]?.startTime,const end = performance.getEntriesByName('end')[0]?.startTime,再做减法。但实际有坑:
end mark 可能还没执行,getEntriesByName('end') 返回空数组,导致 undefined 参与计算,结果为 NaN
measure 生成的 entry 更稳定,且支持 entry.duration 直接读取毫秒值getEntriesByName 返回的是数组,需自己处理索引和时序,不如 measure 语义明确、防错性强不能在 mark 后立刻 sendBeacon,因为 measure 往往滞后;也不宜等页面 unload,可能丢失。推荐在 measure 创建后立即提取并上报:
measure 类型 entry:new PerformanceObserver + observe({ entryTypes: ['measure'] })
if (entry.name.startsWith('page_') || entry.name.includes('api_'))
navigator.sendBeacon 上报,确保不阻塞主线程:navigator.sendBeacon('/log', JSON.stringify({ type: 'measure', name: entry.name, duration: entry.duration, startTime: entry.startTime }))
sendBeacon 仅支持 POST,且 payload 大小受浏览器限制(通常 ≤64KB),大体积日志需分片或节流最容易被忽略的一点:mark 的命名必须全局唯一且可追溯。比如 'render_start' 在多个组件里重复使用,后续根本分不清是哪个模块的耗时。建议结合上下文生成命名,例如 `render_start_${componentName}_${timestamp}`,或者统一走业务埋点 SDK 封装,避免裸调 API。