高分辨率时间接口

698 阅读2分钟

简介

定义

高分辨率时间接口(High Resolution Time level 3) 提供了不受系统时钟偏差影响的起始时间记录(time origin)和亚毫秒级计时接口(DOMHighResTimeStamp)

【扩展】

  1. 原文亚毫秒(sub-millisecond)指精度高于毫秒,目前实现精度为微秒(1微妙等于百万分之一秒,10^(-6),1毫秒等于千分之一秒,10^(-3));
  2. Date 为毫秒近似计时
背景

ECMA-262 定义的 Date 对象记录自 1970 年 1 月 1 日 00:00:00 (UTC) 到当前时间的毫秒数,时间戳类型为DOMTimeStamp

存在问题
  1. 不是稳定的单调时钟,会根据目标系统的不同出现偏差(如不同worker之间的起始时间记录)
  2. 不能提供亚秒级的时间记录
解决方案:

基于以上问题,High Resolution Time提供精度更高(亚毫秒)、更稳定的performance接口,其时间戳类型为DOMHighResTimeStamp

performance接口暴露在worker 和 window全局中,提供了系列高性能计算接口,其中performance.timeOrigin记录了稳定的起始时间

定义对象

Time Origin
起始时间计算方式:
  1. 对于window 对象,
    • 如果没有前document,timeOrigin表示从浏览器上下文建立时计时
    • 如果有前document且unload事件完毕,timeOrigin表示用户确认前页面unload的时间点?
    • 其他情况为加载window最新document时间
  2. 对于WorkerGlobalScope全局对象,timeOrigin是worker创建的时间
  3. 其他情况, time origin为undefined
DOMHighResTimeStamp:

double类型的时间计量类,精确至【5微妙】

数据结构
typedef double DOMHighResTimeStamp;
Performance
数据结构
interface Performance : EventTarget {    
	DOMHighResTimeStamp now();
    readonly attribute DOMHighResTimeStamp timeOrigin;
    [Default] object toJSON();
};
基本使用

Eamples:比较全局时间和worker中的相对时间

/** worker.js */
onconnect = function(e) {
    const port = e.ports[0];

    port.onmessage = async function(e) {
        // worker 执行任务
        const task_start = performance.now();
        const result = await new Promise((resolve) => {
            setTimeout(() => {
                resolve(e.data)
           }, 1000)
       })
        const task_end = performance.now();
        
        // 发送epoch-relative时间戳至其他上下文
        const date = Date.now()
        const timeOrigin = performance.timeOrigin

        port.postMessage({ 
            'w_task': 'Some worker task',
            'w_task_start': task_start,
            'w_task_end': task_end,
            'w_timeOrigin_add_start_time': task_start + timeOrigin,
            'w_timeOrigin_end_time': task_end + timeOrigin,
            'w_timeOrigin': timeOrigin,
            'w_date': date,
            'index': result,
       });
   }
 }

/** index.js */
const both = document.querySelector('#both');
function reportEventToAnalytics(obj) {
    console.log(obj)
}

// 转换 worker 时间戳为document 的 time origin 时间戳
if (!!window.SharedWorker) {
    const worker = new SharedWorker('worker.js');

    both.onclick = function() {
        const performanceNow = performance.now()
        const dateNow = Date.now()
        const timeOrigin = performance.timeOrigin
        console.log('SendMessage to worker: ', performanceNow, dateNow, timeOrigin)
        worker.port.postMessage({origin: 'both', dateNow, performanceNow,timeOrigin})
   }

    worker.port.onmessage = function (event) {
        console.log('Receive message from worker.js')

        const msg = event.data;
        reportEventToAnalytics(Object.assign({}, msg));

        const timeOrigin = performance.timeOrigin
        // 转换 epoch-relative 时间戳为docuemnt的 time origin
        msg.index_start_time = msg.w_timeOrigin_add_start_time - timeOrigin;
        msg.index_end_time = msg.w_timeOrigin_end_time - timeOrigin;

        reportEventToAnalytics(msg);
   }

    worker.port.start()
} else {
    console.log(`Your brower doesn't support SharedWorker API`)
}

在worker中,timeOrigin保持在一个固定值,所以可以用timeOrigin计算较准确的时间点

【扩展】经测试,(performance.timeOrigin+performance.now()) - Date.now() 两小时的误差约 8毫秒

参考

High Resolution Time Level 3: www.w3.org/TR/hr-time-…