performance.now() 的小数部分不可舍去,因其返回高精度浮点数(单位毫秒,分辨率可达微秒级),真实反映时间粒度;舍入会掩盖性能抖动、误判帧率阈值(如16ms)、干扰p95等统计分析。

为什么 performance.now() 的小数部分不能舍去
因为 performance.now() 返回的是 DOMHighResTimeStamp 类型,本质是浮点数,单位毫秒但分辨率可达微秒级(Chrome 下常见 0.001~5μs 步进)。这不是“误差”,而是真实时间粒度——比如在渲染循环中,0.3ms 差异就可能让一帧从 60fps 掉到 59fps。
实操建议:
- 日志调试时用 .toFixed(3) 看三位小数,方便比对
- 做性能阈值判断(如“是否超 16ms”)直接用原始浮点数比较,别 Math.round() 后误判
- 批量采集耗时数据时保留原始值,后续再聚合(如 p95、标准差),否则四舍五入会掩盖抖动特征
如何避免异步操作污染测量结果
常见错误现象:在 await 前后调 performance.now(),却把网络延迟、服务端处理时间全算进“算法耗时”里。
使用场景举例:测一个数据解析函数的纯执行开销,而非整个请求链路。
- ✅ 正确做法:把 performance.now() 放进异步回调内部,例如 then(() => { const end = performance.now(); }) 或 await api(); const end = performance.now();
- ❌ 错误写法:const start = performance.now(); await api(); const end = performance.now(); —— 这测的是端到端延迟
- 若需分离“网络 + 解析”,可在 fetch().then(res => res.json()) 后立即打点,再在 .then(data => parse(data)) 内部测解析
怎么用 performance.now() 做可靠基准测试(非简单单次测量)
单次测量受 JIT 编译、GC、CPU 频率波动影响大,无法反映真实稳定性。必须重复多次并观察分布。
实操建议:
- 至少运行 100 次以上,取中位数或 p95 而非平均值(避免 GC 尖峰拉高均值)
- 每轮前加预热:先跑 10~20 次不计时,让 V8 完成优化编译
- 避免在 requestAnimationFrame 回调里高频打点(虽开销极低,但日志刷屏易掩盖真实瓶颈)
- 示例节选:
function benchmark(fn, times = 100) {<br> // 预热<br> for (let i = 0; i const durations = [];<br> for (let i = 0; i const start = performance.now();<br> fn();<br> const end = performance.now();<br> durations.push(end - start);<br> }<br> return durations.sort((a, b) => a - b)[Math.floor(times * 0.95)]; // p95<br>}
为什么 DevTools Performance 面板里看不到 performance.now() 的数值
performance.now() 只返回数值,不自动注册到 Performance Timeline。它轻量、无副作用,但也因此无法在面板里对齐渲染帧、网络请求等事件。
若需可视化对齐分析(比如确认某次数据处理是否卡住了首屏渲染),必须配合:
- performance.mark('start') 和 performance.mark('end')
- 再用 performance.measure('label', 'start', 'end') 注册可追踪的测量项
- 记得定期调 performance.clearMarks(),否则长期页面 mark 积累上千个会拖慢 DevTools 加载
真正难的不是拿到那个带小数的时间戳,而是确保每次测量都隔离了干扰变量——JIT 状态、内存压力、CPU 调频、甚至同一时刻其他标签页的负载。这些细节不控制,微秒级精度反而会给出虚假的确定性。










