必须用 performance.now(),因其精度达微秒级、不随系统时间调整跳变、开销低,适合在 click/input/scroll 等交互中准确测量真实响应延迟。

直接用 performance.now() 就行,它专为这类场景设计——精度高、不跳变、开销极低,特别适合在用户交互(如 click、input、scroll)中抓取真实响应延迟。
为什么事件处理里必须用 performance.now()?
事件响应耗时对用户体验极其敏感(比如 100ms 内无反馈就感知卡顿),而普通方法容易失真:
- Date.now() 只有毫秒级精度,且系统时间被手动调整时会返回负值或突变,导致“响应耗时 -12ms”这种荒谬结果
- console.time() 是调试辅助工具,非标准 API,部分环境不支持、不可用于生产埋点,且无法跨异步链路稳定采集
- performance.now() 返回浮点数(如 1245.678),实际分辨率常达微秒级,能区分出 0.3ms 的差异——这对判断是否压线 16ms(60fps 帧预算)至关重要
在事件回调中正确打点的写法
关键原则:起始时间必须在事件触发**第一时间**记录,不能等事件委托冒泡完成、也不能等防抖节流逻辑执行完;结束时间要落在业务逻辑真正收尾处(比如 DOM 更新后、状态提交后)。
- 基础模式(同步逻辑):
```js
button.addEventListener('click', () => {
const start = performance.now();
doSomething(); // 如更新 state、操作 DOM
const end = performance.now();
console.log(`click handler took ${end - start}ms`);
});``` - 含异步操作(如 fetch 后渲染):
把 end 放进then或await后,否则测的是“发请求耗时”,不是“用户看到结果的耗时”:
```js
searchInput.addEventListener('input', async () => {
const start = performance.now();
const data = await fetchSuggestions();
renderResults(data); // 真正影响用户的一步
const end = performance.now();
reportMetric('search-response', end - start);
});```
避免常见陷阱
- 别在
setTimeout(() => {...}, 0)外层记 start —— 这样测的是“事件排队等待时间”,不是函数执行本身 - 别在 React 的 useEffect 或 Vue 的 onMounted 里补测 —— 事件已过去,时间原点错位,数据不可比
- 小数不用
Math.round():0.002ms 的抖动可能反映主线程争抢,四舍五入会掩盖性能毛刺;日志可.toFixed(3)便于阅读,但阈值判断(如> 16)必须用原始浮点数 - 高频事件(如 scroll、mousemove)慎用 console.log 打点 —— 改用采样(每 5 帧一次)或批量上报,避免日志刷屏拖慢页面
进阶:和 Performance Timeline 对齐分析
如果想在 Chrome DevTools 的 Performance 面板里看到这段耗时,并和渲染帧、Layout、Paint 对齐,需配合 mark 和 measure:
- ```js
button.addEventListener('click', () => {
performance.mark('ui-click-start');
doSomething();
performance.mark('ui-click-end');
performance.measure('ui-click-total', 'ui-click-start', 'ui-click-end');
});``` - 这样打点后,在 DevTools → Performance → Record → 查看 User Timing 轨道,就能直观看到该次点击耗时与 FPS、GC 的关系
- 记得定期调
performance.clearMarks(),防止 mark 积累过多拖慢 DevTools 加载

















