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

boyanx5个月前技术教程22

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

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

标签: js日期

相关文章

JavaScript需要掌握的技能盘点(JS入门需看)

JavaScript是当今使用的最重要的 Web 开发语言之一。它使您可以为您的网站添加广泛的功能特性,从最基本得到最高级的。因此,无论您是专家级开发人员还是刚起步的初学者,您都需要了解某些关键的Ja...

记录一个函数执行了多长时间?分享 1 段优质 JS 代码片段!

本内容首发于工粽号:程序员大澈,每日分享一段优质代码片段,欢迎关注和投稿!大家好,我是大澈!本文约 500+ 字,整篇阅读约需 1 分钟。今天分享一段优质 JS 代码片段,能够记录一个函数执行了多长时...

Three.js建模基础(three.js 建模)

在Three.js中,一个可见的物体是由几何体和材料构成的。在这个教程中,我们将学习如何从头开始创建新的网格几何体,研究Three.js为处理几何对象和材质所提供的相关支持。1、索引面集/Indexe...

JavaScript 定时器和延时器(js定时器的原理)

关于定时器setInterval(code, millisecond)和延时器setTimeout(code, millisecond)中第一个参数引号问题思考对于自定义函数使用双引号必须加上括号;s...

js时间插件Dayjs相对Momentjs的优势

js时间插件Day.js和Moment.js区别昨天发了一些插件的汇总,在评论中有个网友建议把Moment.js换成Day.js,今天特意去学习了Day.js,发现比Moment.js强大很多首先看一...

js计算两个时间相差(js计算两个时间的时间差)

DateDifference (faultDate, completeTime) { var stime = new Date(faultDate).getTime() var etime = new...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。