最轻量可控的方式是用performance.mark()和performance.measure()打点并用getEntriesByType('measure')提取数据;需在关键节点插入语义化标记(如'search-submit-start'),成对调用measure计算耗时,配合PerformanceObserver监听或beforeunload时上报,注意清理与兼容性。

直接用 performance.mark() 和 performance.measure() 给业务流程打点,再通过 getEntriesByType('measure') 提取数据,是最轻量、最可控、也最贴近真实用户场景的方式。
在关键业务节点插入语义化标记
标记名不是随便起的,要能一眼看出阶段和意图,比如:
-
搜索发起前 →
performance.mark('search-submit-start') -
接口响应后 →
performance.mark('search-api-response') -
首屏卡片渲染完成 →
performance.mark('search-results-visible')
命名统一用小写字母 + 短横线,不拼接动态 ID(如 'item-' + id),否则后续无法聚合统计 p95 或做趋势分析。
用 measure 显式计算耗时区间
不要依赖默认时间或异步回调里的 performance.now(),必须成对使用 mark 才可靠:
- 在函数入口打起始标记,出口或 DOM 更新后打结束标记
- 立刻调用
performance.measure('search-total', 'search-submit-start', 'search-results-visible') - 同一链路中,“准备→触发→响应→渲染→可见”每个阶段都独立命名,不复用
如果某个 mark 缺失,measure 会静默失败——上线前可用 performance.getEntriesByName('xxx', 'mark').length 验证是否真正落点成功。
自动采集并可靠上报数据
标记和测量结果只存在内存里,页面关闭就丢。必须主动拉取并发送:
- 推荐用 PerformanceObserver 监听
measure类型,一生成就捕获 - 上报字段至少包含:
name(如search-total)、duration、pathname、device、effectiveType(网络类型) - 用
navigator.sendBeacon()上报,避免因页面卸载导致丢失
也可以在关键操作完成或 beforeunload 时批量提取:performance.getEntriesByType('measure').filter(m => m.name.startsWith('search-'))。
注意清理与兼容性
长期运行的 SPA 项目需定期清理,防止内存堆积:
- 在路由切换或模块卸载时调用
performance.clearMarks()和performance.clearMeasures() - 异步逻辑中,
mark必须在await后或回调内打,否则测的是调度延迟而非真实执行 - Safari 支持大部分 API,但部分字段(如某些 resource timing)需服务端配
Timing-Allow-Origin才可见


















