Performance API:用 performance.now() 和 performance.mark() 做精确测量
聊到前端性能监控,很多人第一反应是「打开 Chrome DevTools 看 Network 面板」。嗯,这当然没错。但你想过没有——线上用户的真实体验,你怎么测?
我个人的习惯是,永远不要依赖开发环境的数据。本地网络快、CPU 空闲、浏览器缓存干净,这些条件跟用户的实际环境完全是两码事。所以,我们需要一套能在生产环境里跑起来的精确测量工具。
Performance API 就是干这个的。今天咱们重点聊两个最常用的方法:performance.now() 和 performance.mark()。
1. performance.now():比 Date.now() 靠谱得多
你可能写过这样的代码:
const start = Date.now();
// 执行一些操作
const end = Date.now();
console.log(`耗时:${end - start}ms`);
这有什么问题?问题大了去了。
Date.now() 依赖系统时间。如果用户电脑的系统时间被 NTP 同步调整了,或者用户手动改了时间,你的测量结果就全乱了。更坑的是,Date.now() 的精度只有毫秒级,对于微秒级别的操作根本测不准。
performance.now() 就不一样了。它返回的是从页面加载开始到当前时刻的毫秒数,精度可以达到微秒级(实际是浮点数,小数点后最多 5 位)。而且它不受系统时间影响,是一个单调递增的时间戳。
核心区别一句话:Date.now() 看「墙上的钟」,performance.now() 看「秒表」。
来看个实际例子。我在项目中曾经需要测量一个复杂动画每一帧的渲染耗时:
function measureFrame() {
const start = performance.now();
requestAnimationFrame(() => {
// 执行渲染逻辑
doHeavyRender();
const end = performance.now();
console.log(`帧渲染耗时:${(end - start).toFixed(2)}ms`);
if (end - start > 16.67) {
console.warn('⚠️ 掉帧了!当前帧耗时超过 16.67ms');
}
});
}
你看,这里用 performance.now() 才能精确捕捉到 16.67ms 这个阈值。换成 Date.now(),你测出来的结果可能永远是 0ms 或 1ms——因为 Date 的精度根本不够。
小技巧:performance.now() 返回的是 DOMHighResTimeStamp 类型,你可以直接用减法计算差值。而且它支持 performance.timeOrigin 属性,能拿到页面启动时的绝对时间戳。
2. performance.mark():给时间线打上「书签」
如果说 performance.now() 是秒表,那 performance.mark() 就是给秒表打标记。你可以在代码的关键节点上打上标记,然后通过 performance.measure() 计算两个标记之间的耗时。
我曾经接手过一个老项目,页面加载慢得离谱。但代码里到处都是零散的 console.time,根本理不清到底哪里慢。后来我用 performance.mark() 重构了测量逻辑:
// 在关键节点打标记
performance.mark('data-fetch-start');
fetch('/api/user/data')
.then(res => res.json())
.then(data => {
performance.mark('data-fetch-end');
// 处理数据
performance.mark('data-process-start');
const processed = heavyProcess(data);
performance.mark('data-process-end');
// 计算耗时
performance.measure('数据请求', 'data-fetch-start', 'data-fetch-end');
performance.measure('数据处理', 'data-process-start', 'data-process-end');
// 获取测量结果
const measures = performance.getEntriesByType('measure');
measures.forEach(m => {
console.log(`${m.name}:${m.duration.toFixed(2)}ms`);
});
// 清理标记(避免内存泄漏)
performance.clearMarks();
performance.clearMeasures();
});
这样一搞,整个流程的瓶颈一目了然。我记得当时发现「数据处理」阶段竟然占了 80% 的耗时,而「数据请求」反而很快。于是我把优化重点放在了数据处理逻辑上,而不是去折腾网络请求。
注意:performance.mark() 创建的标记会一直保留在浏览器的性能缓冲区里。如果不及时清理,随着页面运行时间变长,内存占用会逐渐增加。建议在测量完成后调用 performance.clearMarks() 和 performance.clearMeasures()。
3. 实战:用 Performance API 监控组件渲染性能
光说不练假把式。咱们来写一个真实可用的 React 组件性能监控 hook:
function useRenderTimer(componentName) {
const renderStart = useRef(null);
useEffect(() => {
// 组件挂载时打标记
performance.mark(`${componentName}-mount-start`);
return () => {
performance.mark(`${componentName}-mount-end`);
performance.measure(
`${componentName} 挂载耗时`,
`${componentName}-mount-start`,
`${componentName}-mount-end`
);
const measure = performance.getEntriesByName(
`${componentName} 挂载耗时`
)[0];
if (measure && measure.duration > 100) {
console.warn(
`⚠️ ${componentName} 挂载耗时 ${measure.duration.toFixed(2)}ms,建议优化`
);
}
// 上报到监控系统
reportMetric(componentName, measure.duration);
performance.clearMarks();
performance.clearMeasures();
};
}, []);
// 记录每次渲染的耗时
renderStart.current = performance.now();
useEffect(() => {
const duration = performance.now() - renderStart.current;
if (duration > 50) {
console.warn(`⚠️ ${componentName} 渲染耗时 ${duration.toFixed(2)}ms`);
}
});
}
这个 hook 可以自动监控组件的挂载耗时和每次渲染耗时。当耗时超过阈值时,它会发出警告。你还可以把数据上报到你的监控平台,形成线上性能看板。
4. 避坑指南:我踩过的几个坑
用 Performance API 这么多年,我踩过不少坑。分享几个最常见的:
- 标记名称冲突:不同模块用了相同的标记名,导致测量结果混乱。我后来强制要求所有标记名带上模块前缀,比如
UserList-mount-start。 - 忘记清理标记:有一次线上页面跑了几个小时,性能缓冲区被撑爆了,新标记打不进去。从那以后我养成了「用完即清」的习惯。
- 在 Service Worker 里用 performance.now():Service Worker 里的
performance.now()返回的是相对于 Worker 启动的时间,跟主线程的时间戳不能直接比较。这个坑我查了整整一天才找到原因。 - 高精度时间戳被限制:出于安全考虑,浏览器可能会降低
performance.now()的精度(比如 Chrome 会将其四舍五入到 100 微秒)。如果你的测量需要纳秒级精度,可以考虑用performance.timeOrigin结合Date.now()做校准。
5. 总结一下
performance.now() 和 performance.mark() 是前端性能测量的基石。它们比 Date.now() 和 console.time 更精确、更可靠,而且能跟浏览器的 Performance Timeline 无缝集成。
我个人建议:从现在开始,把所有 Date.now() 的测量代码都换成 performance.now()。别嫌麻烦,线上用户不会给你重测的机会。
下一章咱们聊聊 PerformanceObserver——如何被动监听性能事件,而不需要手动打标记。这个 API 在监控第三方脚本性能时特别好用。