performance.mark()和performance.measure()需绑定生命周期钩子打点、严格配对使用、统一命名规范、节流抽样上报、分层归因并聚合P95/P99指标,才能构建可靠业务性能大盘。
performance.mark() 和 performance.measure() 是建立业务全链路性能大盘最直接可用的两个原语,但光调用它们远远不够——关键在于标记时机是否对齐业务语义、上报逻辑是否抗压、指标是否可聚合。
业务代码里随意插入 performance.mark('search_start') 很容易失效:比如组件还没挂载、异步请求未触发、或标记被重复执行多次。真正可靠的打点必须绑定到明确的生命周期钩子或事件边界上。
click 事件回调开头打 performance.mark('search_click'),在接口 fetch 的 then 或 response 回调里打 performance.mark('search_response')
DOMContentLoaded,而是在 SSR 渲染完成、关键 DOM 节点(如 #app)已存在且内容非空时,用 requestIdleCallback 延迟打 performance.mark('fsp_rendered')
performance.measure() 必须配对使用performance.measure() 本身不记录时间,它只是基于两个已有 mark 名称计算差值并生成一条 PerformanceMeasure 条目。如果某一个 mark 没打上,measure 就会静默失败,控制台无报错,但数据永远缺失。
measure 的两个参数名都已存在,可通过 performance.getEntriesByName('xxx', 'mark') 验证'{业务模块}_{阶段}_{后缀}':如 'checkout_submit_start'、'checkout_submit_end'、'checkout_submit_duration'(后者为 measure 名)mark 名——同一名称多次调用会保留全部时间戳,getEntriesByName 返回数组,measure 默认取第一个,易出错前端性能数据天然稀疏、低频,但一旦进入真实用户环境,量级陡增。用 console.log 查看没问题,一到上报就大量丢失,往往卡在这三处:
fetch 同步上报:页面卸载前触发,但 fetch 可能被中断。必须改用 navigator.sendBeacon(),且 payload 控制在 64KB 内performance.getEntriesByType('measure') 会累积所有历史 measure,不清理会导致内存缓慢增长、后续查询变慢。每次上报后调用 performance.clearMarks() 和 performance.clearMeasures()
单纯把 search_response - search_click 当作“搜索耗时”是危险的——它混入了网络延迟、服务端处理、前端解析、渲染等所有环节,无法定位瓶颈。真正可行动的指标需要分层剥离:
performance.getEntriesByType('resource') 找出对应搜索接口的 responseEnd - requestStart
fetch().then 里用 performance.now() 手动测 JSON.parse + 状态更新耗时performance.getEntriesByType('paint') 判断是否因重排/重绘导致响应滞后performance.mark() 和 performance.measure() 本身极轻量,但串联成链路、对齐业务、稳定上报、分层归因,每一步都有隐性成本。最容易被忽略的是:标记名不是随便起的字符串,而是业务语义的契约;measure 不是自动计时器,而是显式声明的因果关系。