应使用 Performance.now() 而非 Date.now() 测量异步任务耗时,因其基于高精度单调时钟,不受系统时间调整影响;起始时间须打在任务体内部(如 then 回调内)而非注册时,才能准确反映实际执行开销。

直接用 Performance.now() 在异步任务前后打点,就能准确测出其在事件循环中实际耗时,避开系统时间抖动和 setTimeout 误差。
为什么不能只用 Date.now()?
Date.now() 返回的是系统时间戳,受系统时钟调整、NTP 同步、CPU 节能策略等影响,两次调用差值可能不准。而 Performance.now() 基于高精度单调时钟(monotonic clock),不受系统时间跳变干扰,分辨率通常达微秒级,更适合测量短时异步行为。
在 Promise.then / async/await 中正确打点
关键是在异步任务“真正开始执行”的位置记录起始时间,而不是在注册回调时。例如:
const start = performance.now();
<p>Promise.resolve().then(() => {
const taskStart = performance.now(); // ✅ 这里才是任务实际执行起点
// 模拟一段耗时操作
let sum = 0;
for (let i = 0; i < 1e7; i++) sum += i;
const end = performance.now();
console.log(<code>Promise 任务耗时: ${end - taskStart}ms</code>);
});</p><p>// ❌ 错误:start 写在 then 外面,测的是注册到执行的排队延迟,不是任务本身
测量宏任务排队延迟(如 setTimeout、setImmediate)
想看一个宏任务从被调度到真正执行花了多久(即在消息队列里等了多久),可以把打点放在调度前后 + 执行时:
立即学习“Java免费学习笔记(深入)”;
- 调度前记一次:
performance.now() - 回调执行第一行再记一次:
performance.now() - 两者差值 ≈ 排队延迟(含事件循环轮转开销)
示例:
const scheduledAt = performance.now();
setTimeout(() => {
const executedAt = performance.now();
console.log(`setTimeout 排队耗时: ${executedAt - scheduledAt}ms`);
}, 0);
结合 PerformanceObserver 监控长任务(Long Tasks)
浏览器原生支持用 PerformanceObserver 捕获执行超 50ms 的任务(可能造成卡顿),无需手动打点:
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log(`长任务耗时: ${entry.duration}ms`, entry);
});
});
observer.observe({ entryTypes: ["longtask"] });
注意:需在页面早期启用,且仅适用于主线程长时间运行的任务(如复杂渲染、大量 JS 计算),不适用于微任务或快速 Promise 链。
核心就两点:用 performance.now() 替代 Date.now(),把起始时间戳打在任务体内部而非注册位置。这样测出来的才是事件循环中真实执行开销。


















