HTML中performance.mark标记性能节点
排查性能监控问题时,一个常见且令人困惑的场景是:明明代码里调用了 performance.mark() 打了点,但后续用 performance.getEntriesByName() 却怎么也查不到这条记录。问题出在哪里? performance.mark() 为什么打完点却 getEntriesB
排查性能监控问题时,一个常见且令人困惑的场景是:明明代码里调用了 performance.mark() 打了点,但后续用 performance.getEntriesByName() 却怎么也查不到这条记录。问题出在哪里?

performance.mark() 为什么打完点却 getEntriesByName 拿不到?
根本原因通常不是 API 本身失效,而是标记压根没有成功“注册”到性能时间线里。一次成功的 performance.mark() 调用,需要满足几个基本条件:标记名称必须是合法字符串、函数调用确实被执行了(而非被优化工具移除)、并且没有被后续的 performance.clearMarks() 清理掉。
实践中,下面几种情况最容易导致标记“消失”:
- 构建工具的“优化”:像 Terser 这类代码压缩工具,有时会将孤立的
performance.mark('xxx')语句判定为无副作用代码而直接删除。一个简单的防御技巧是加上void 0兜底:performance.mark('xxx'); void 0;。 - 变量拼接的标记名:使用
performance.mark(prefix + '-start')这类动态名称时,如果变量prefix在压缩后被替换为短变量名,可能导致前后名称不一致,从而无法匹配查询。 - 异步时序错乱:在异步逻辑中,如果“读”操作跑在了“写”操作前面,自然会拿到空结果。例如:
setTimeout(() => { performance.mark('a'); }, 0); console.log(performance.getEntriesByName('a'));,控制台输出几乎总是空数组。 - IE11 的兼容性陷阱:IE11 虽然支持
mark(),但不支持clearMarks()。如果代码中尝试清理标记失败,残留的旧数据可能会干扰新标记的识别和查询。
如何安全配对 performance.mark 和 performance.measure?
需要明确一点:performance.measure() 并非一个自动计算时长的工具。它的核心工作只是根据两个已存在的标记名称,查找它们对应的 startTime 并计算差值,从而生成一条新的 PerformanceMeasure 条目。如果起点或终点的标记有任何一方缺失,measure 就会静默失败,既不报错,也不会留下任何记录。
要确保配对可靠,有几个实操要点必须注意:
- 显式检查起点存在性:在创建结束标记和测量之前,先确认起点标记已就位。例如:
if (performance.getEntriesByName('api-start').length) { performance.mark('api-end'); performance.measure('api-latency', 'api-start', 'api-end'); } - 名称严格匹配:标记名称区分大小写和空格。
'Api-Start'和'api-start'会被视为两个完全不同的标记,measure()不会进行模糊匹配。 - 避免重复名覆盖:对同一个名称多次调用
mark(),只会保留最后一次调用的时间戳。对于可能重复的打点场景,建议为名称添加唯一后缀,例如使用时间戳`load-${Date.now()}`或随机字符串。 - 参数类型要正确:
measure()只接受字符串类型的标记名作为参数。不要误将performance.timing对象中的数值时间戳(如navigationStart)直接传给它。
为什么不能只用 console.time 替代 performance.mark?
虽然两者都能用于计时,但 console.time() 本质上是一个调试辅助工具,而 performance.mark() 是 Web 标准定义的专业性能度量接口。它们的定位和能力层级完全不同。
关键差异体现在以下几个方面:
- 上下文稳定性:
console.time('x')的计时上下文在复杂的异步操作(如 Promise 链中断、跨 iframe 通信、React 异步渲染模式)中可能丢失或错位,可靠性不足。 - 工具链支持:Chrome DevTools 的性能面板、Lighthouse 审计工具以及各类 RUM(真实用户监控)SDK,都原生识别和处理
performance.getEntriesByType('mark')收集的数据,但完全无法捕获console.time的记录。 - 时间基准统一:
performance.mark()生成的时间戳基于页面导航起点(navigationStart),这使得它能与首次绘制(FP)、首次内容绘制(FCP)、DOM 加载完成等原生性能指标对齐分析。而console.time的计时起点是相对当前 JavaScript 执行时刻的任意偏移,难以进行横向对比。 - 数据可编程性:
console.time的数据无法以结构化的方式导出,也无法通过PerformanceObserverAPI 进行自动化的监听和上报。
上报 measure 数据时最容易漏掉的三件事
很多团队虽然实现了打点和测量,但监控后台却收不到数据,问题往往卡在最后的上报环节。以下三个细节必须逐一确认:
- PerformanceObserver 配置必须完整:在监听
measure条目时,必须显式配置{ entryTypes: ['measure'] }选项。缺少这个配置,回调函数永远不会被触发,这是最容易被忽略的一步。 - 上报时机不能依赖页面卸载:使用
window.onunload或beforeunload事件发送日志是危险的。在页面快速跳转或浏览器崩溃时,这些请求很可能丢失。更可靠的做法是在measure条目创建后立即提取数据,并使用navigator.sendBeacon()方法发送,该方法专为可靠、异步的小数据传输设计。 - 高频打点需要节流:在滚动、输入等高频交互场景中,可能瞬间产生大量
measure记录。如果每条都立即发起网络请求,会阻塞主线程并可能触发浏览器的请求限流。合理的策略是对数据进行聚合(例如每 500ms 批量发送一次),或利用requestIdleCallback在浏览器空闲时段异步发送。
真正的难点,从来不是执行一次 mark() 或 measure() 调用这个动作本身,而是如何确保每一个标记都能精确对应一段可复现的用户行为切片,并且保证从打点、测量到上报的整个链路没有时序断点或命名歧义。任何一个环节使用了不稳定的变量命名、忘记了清理旧标记、或是配置 PerformanceObserver 时漏掉了关键选项,都可能导致整套性能监控体系出现盲区。
