专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

为什么前端大佬都推荐用 performance.now() 而非 Date.now()?

ins518 2025-07-06 12:44:17 技术文章 4 ℃ 0 评论

家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。

1. 为什么需要 Performance API

Performance API 能保证达到亚毫秒级(小于 1ms)分辨率的时间和稳定的单调时钟,同时 不受系统时钟偏差或调整的影响。

// Performance interface
[Exposed=(Window,Worker)]
interface Performance : EventTarget {
    DOMHighResTimeStamp now();
    readonly attribute DOMHighResTimeStamp timeOrigin;
    [Default] object toJSON();
};

Performance API 通过 DOMHighResTimeStamp 类型来实现高精度计时,其单位为毫秒,精度为 5 us(微秒),用双精度类型 (double) 存储。但如果浏览器由于硬件、软件限制或安全、隐私等无法提供精确到 5 微秒的时间时,则可以将该值表示为精确到毫秒的时间值。

DOMHighResTimeStamp 可用于描述离散的时间点或时间间隔,而起始时间可以是网站或应用脚本确定的具体时间,也可以是时间原点,即 timeOrigin。

2. Performance API 中的 timeOrigin 如何界定

Performance API 使用 Performance.timeOrigin 来确定与性能相关的时间戳的基准,所有 DOMHighResTimeStamp 时间都相对于 timeOrigin 属性。

在 Window 上下文中,该时间是导航开始的时间,而在 Worker 和 ServiceWorker 上下文中则表示 Worker 运行的时间。

// Level 1 标准会有时钟变化的风险
currentTime = performance.timing.navigationStart + performance.now();
// Level 2 避免了时钟变化的风险
currentTime = performance.timeOrigin + performance.now();

timeOrigin 经过了两个核心阶段,即 Level 1 和 Level 2 规范:

  • Level 1 规范:performance.now() 是相对于导航计时规范中的 performance.timing.navigationStart 属性
  • Level 2 规范:performance.now() 相对于 Performance.timeOrigin,从而避免了跨网页比较时间戳时时钟变化的风险

同时,为了解决 Window 和 Worker 上下文中不同的时间来源差异,开发者可以借助 performance.timeOrigin 转换来自 Worker 脚本的时间戳以便应用程序时间保持同步。

// worker.js
self.addEventListener("connect", (event) => {
  const port = event.ports[0];
  port.onmessage = function (event) {
    const workerTaskStart = performance.now();
    // doSomeWork()
    const workerTaskEnd = performance.now();
  };
  // 将特定 worker 的时间戳转化为绝对时间,同时通知到主页面
 //  performance.timeOrigin 表示 worker 运行的时间
 // performace.now 都是相对于 performance.timeOrigin 计算
  port.postMessage({
    startTime: workerTaskStart + performance.timeOrigin,
    endTime: workerTaskEnd + performance.timeOrigin,
  });
});

下面是 main.js 的内容:

const worker = new SharedWorker("worker.js");
worker.port.addEventListener("message", (event) => {
  // 将绝对时间转化为相对于 window 的时间戳
  const workerTaskStart = event.data.startTime - performance.timeOrigin;
  const workerTaskEnd = event.data.endTime - performance.timeOrigin;
  console.log("Worker task start:", workerTaskStart);
  console.log("Worker task end:", workerTaskEnd);
});

需要注意的是,performance.timeOrigin 的值可能与在时间原点执行的 Date.now() 返回的值不同,因为 Date.now() 可能受到系统和用户时钟调整、时钟偏差等的影响,而 timeOrigin 属性是一个单调时钟,当前时间永远不会减少,也不受各种调整的影响。

3.Performance.now() 与 Date.now() 有什么不同

Date.now() 表示为自纪元以来经过的毫秒数,纪元为 1970 年 1 月 1 日凌晨 12 点,表示 UTC 时间,而 performance.now() 方法则与 Performance.timeOrigin 相关。

Date.now();
// 输出 1678889977578
performance.now();
// 输出 233936

JavaScript 时间会受到系统时钟偏差或调整的影响,即时间值始终单调递增的假设会被打破。Date 对象的主要用途是向用户显示时间和日期信息,因此操作系统会运行一个守护进程来定期同步时间,即时钟可能会每小时调整几次,而每次调整几毫秒。

相比之下,performance.now() 提供单调递增的时间值且不受时钟调整的影响。这意味着可以保证 DOMHighResTimeStamp 值至少等于上次访问时的值。

因此为了测量性能、计算精确的帧速率(FPS)、动画循环等,建议开发者使用 Performance.now() 而非 Date.now()。

4. 如何避免 Performance API 精度降低

为了防范时序攻击 (Timing Attacks) 和指纹识别 (Fingerprinting),DOMHighResTimeStamp 类型会考虑站点隔离 (Site Isolation) 状态:

  • 隔离环境下的分辨率:5us
  • 非隔离环境下的分辨率:100us

开发者可以使用
Cross-Origin-Opener-Policy (COOP) 和
Cross-Origin-Embedder-Policy (COEP) 标头来实现跨域隔离,例如:

Cross-Origin-Opener-Policy: same-origin
// 同源
Cross-Origin-Embedder-Policy: require-corp

以上设置可以确保顶级文档不会与跨域文档共享浏览上下文组 (Browsing Context Group),此时潜在攻击者在弹出窗口中打开文档时将无法访问全局对象,从而阻止了一系列被称为 XS-Leaks 的跨域攻击。

参考资料

https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp

https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin

https://w3c.github.io/hr-time/#dom-domhighrestimestamp

https://medium.com/@vinaychhabra.dev/exploring-javascript-performance-measurement-with-performance-now-d79cb39dc269

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表