settimeout怎么用_怎么收集并上报页面的性能数据?

这篇文章的主题是“怎么收集并上报页面的性能数据?”,主要目的是分享一下笔者是怎么实现它的。

在这个时代,似乎开发者们都不再关心页面的性能。大家会认为随着网络速度越来越快,用户端设备性能越来越好,只要做好基础设施的搭建,比如脚本的打包,传输时压缩等等,我们就可以不再关心页面的性能。不能否认的是我们的网络和设备,甚至开发使用的基础设施都在变好,但是用户和我们开发时使用的环境并不都是一样的,开发时的环境往往比用户的会好很多。而且即使平均状况都不错,但“好”的比例是多少,“不好”的比例又是多少?我们并不知道。和业务挂钩的话,比如成交率,“好”的成交率是多少,“不好”的成交率又是多少?因为我们都不知道,所以更需要解除这个疑惑,而解除的方法就是收集上报页面的性能数据。

那什么时候开始做这件事情?我的建议是在你的页面上线时就应该做好这件事。因为即使“业务的快速迭代”在初期看来比页面性能更重要,但是收集上报数据这件事的成本并不高啊。看完这篇文章你会有一个自己的判断。

我们先来拆解一下需求,比较容易理解的是可以将需求分为三部分 — 收集、上报和性能数据。所以我们先来说说性能数据,它是什么?它从哪儿获取?

什么是性能数据?

什么是性能数据,我们可以理解为它用来描述页面的行为表现和状态,比如我们会说页面加载地特别快,这个页面运行地特别顺畅、一点都不卡,这是页面的行为表现;又比如说我们的页面打开时一片空白,点了按钮卡死在那了,“空白”、“卡死了”都是在说页面的状态怎么样。所以性能数据应该能够告诉我们,页面的行为表现和状态是怎么样的。

W3C 为我们的浏览器制订了一系列的标准,这些标准推荐浏览器应该暴露哪些性能相关的 API,浏览器应该提供哪些信息来告诉开发者他们的页面性能表现怎么样,页面当时所处的状态等等。

比如 W3C 最早定义了 Navigation Timing、Resource Timing 和 User Timing 分别用来描述页面的导航、资源请求、脚本运行时序相关的信息。如果你想知道 W3C 一共制定了哪些性能相关的标准和它们目前的状态可以在这里查看:webperf-dashboard。

上面说了标准定义了 Navigation Timing、Resource Timing 和 User Timing 这些性能相关的信息,那我们从哪里能够获取到它们呢?Performance Timeline 标准就定义了这样一个接口,用来统一获取页面性能相关信息的方式。它在 performance 对象中提供了 getEntries() 方法来获取这些信息。代码示例:

const perfEntries = performance.getEntries();
for (let i = 0; i < perEntries.length; i++) {
    console.log(`
        Name:       ${perfEntries[i].name}
        Entry Type: ${perfEntries[i].entryType}
        Start Time: ${perfEntries[i].startTime}
        Duration:   ${perfEntries[i].duration}
    `);
}

需要注意的是,getEntries 目前并不支持任何参数,部分文档中的内容可能会造成误导。如果需要通过条目的 name 或 entryType 来筛选,可以使用 getEntriesByName 或者 getEntriesByType 方法。代码示例:

// 筛选出页面中资源请求名为 ‘https://www.xxx.com/get.json’ 的条目
const resourceEntries = performance.getEntriesByName(
    ‘https://www.xxx.com/get.json’,
    ‘resource’,
);

3a39c1bdc0ebcb92a69b8df04893e0f0.png

通过 caniuse 可以查到 getEntries 兼容性已经到了可用的状态,大部分现代浏览器都已经支持这个 API。部分不兼容的浏览器我们可以做降级处理,比如可以通过已废弃的 performance.timingperformance.navigation 来获取 Navigation Timing 导航时序的信息。代码示例:

if (!’getEntries’ in performance) {
    perfEntries.push(pefromance.timing);
}

0a0d63bcfd00bb2a971497ca6ba5d411.png
性能条目类型列表

如果你想知道使用 getEntries() 能获取到哪些类型的性能条目?我们可以在这里查看:Timing Entry Names Registry。如上图所示,它列出了 11 种条目类型,每种条目都包含一个或多个页面性能指标。其中 ‘ availabelFromTimeline’ 一列值为 ‘true’ 表示可以通过 getEntries 方法获取到的条目类型,如果为 ‘false’ 则需要通过 PerformanceObserver 来监测。我们先来过一下这些类型的条目分别包含什么数据:

  • mark:它包含开发者通过调用 performance.mark 标记的信息。
  • measure: 它包含开发者通过调用 perfromance.measure 测量的数据。
  • navigation:它包含页面导航相关的时序信息,包括下图中标记的这些时间点。

b5677ba3e57ee3d0ffd39f590ea9e404.png
导航时序图
  • resource:它包含页面中资源加载的时序信息,包括下图中标记的这些时间点数据。

0583ed7c7c14a549beac833f27bb9430.png
资源时序图
  • longtask:它包含任务队列中执行时间 >50 ms 的任务,这样的任务被称为“长时任务”。
  • paint:它包含页面渲染性能相关的指标,比如FP(首次渲染时间)和FCP(首次内容渲染时间)。
  • element:如果元素设置了属性“elementtiming”,浏览器会记录这些元素的加载和渲染时间,开发者可以通过监听这类条目来获得这些信息。
  • event:它用来监测浏览器输入事件的延迟时间。
  • first-input:它包含页面首次发生的输入事件的延迟信息。
  • layout-shift:它包含页面加载时稳定度相关的信息,具体信息可以查看:https://github.com/WICG/layout-instability。
  • largest-contentful-paint: 它包含页面的LCP(最大内容元素渲染时间)。

上面列举的条目类型中,‘longtask’, 'element’, ‘event’, ‘layout-shift’ 和 ‘largest-contentful-paint’ 类型的条目只能通过 PerformanceObserver 对象监测并获取。那么 PerfromanceObserver 该怎么用呢?直接上代码:

// 设置监听器回调,当有新的条目被记录时,回调函数都会被调用
const ob = new PerformanceObserver((list) => {
    if (list.getEntries().length) {
        perfEntries.push(list.getEntries())
    }
});
ob.observe({ entryTypes: [‘longtask’, ‘element’, ‘event’, ‘layout-shift’, ‘largest-contentful-paint’] });

上报时机

解决了数据内容和来源的问题,我们来看一下该何时上报我们收集的数据?最直接的方式是在 onload 时进行上报,对于页面是后端直出的情况是适用的。代码示例:

// 在页面 load 时发起上报程序
window.onload = () => {
    upload();
}

但大多 SPA 应用可能都不适用,因为可能有一些数据请求是在前端发起的,所以需要等待请求返回之后才能完成页面的渲染。这时候另外一个选择就是在页面卸载时进行上报。但这会带来一些问题,比如:

  • 可能会丢失数据,请求还没完成浏览器就取消掉了;
  • 如果使用同步接口,会阻塞页面的卸载;
  • 浪费用户资源,因为很多数据并不是我们需要的。

所以我们再回到最基本的需求上,我们想要收集的是哪些数据,大多时候我们需要收集的是页面首屏渲染相关的数据。所以我们要找的时机就是首屏渲染完成的时候,这对于 SPA 应用来说可能是个难题。 但我们可以换个思路想,既然 SPA 应用大多时候需要等待请求完成后才会完成首屏渲染,那么是不是可以在所有请求完成后进行上报?除了手动上报之外,还有其他方法可以知道请求都完成了吗?我们可以利用上面讲到的 PerformanceObserver,它可以监听到有没有新的请求条目,如果在足够长的时间内没有获取到新的请求条目,是不是就可以认为资源都已经加载完成了?这在大多数时候是可靠的。代码示例:

const ob = new PerformanceObserver((list) => {
    if (list.getEntries().length) {
        const timer = setTimeout(() => {
            // 如果在一定时间内没有再获取到资源请求的条目
            // 则发起上传程序
            upload();
        }, 500); // 根据应用情况设置足够长的时间
        clearTimeout(timer);
    }
});
ob.observe({ entryTypes: [‘resource’] })

上面说的都是在页面能够成功加载完成的情况,但有时候页面加载时间过长用户直接关闭了页面,这时候我们就无法上报这些“放弃加载”的性能数据,我们统计的数据也无法反应出“放弃加载”的情况。所以我们还需要在页面卸载时发起上传程序。代码示例:

window.onunload = () => { upload() }

如何上报?

我们来看最后一步,如何上报我们收集的页面性能数据?我们可以通过 fetch 或者 XMLHttpRequest 接口,但是因为可能要在页面卸载回调中执行上报程序,所以需要使用同步的接口来实现。可供选择的接口,除了同步的 XMLHttpRequest 以外,我们还可以使用 navigator.sendBeacon 方法来发送我们收集的数据。代码示例:

function sendData(uploadData) {
    if (!sendDataByBeacon(uploadData)) {
        sendDataByXHR(uploadData);
    }
}

function sendDataByBeacon(uploadData) {
    if (’sendBeacon' in navigator) {
        // 发送成功会返回 true
        return navigator.sendBeacon(uploadData));
    }
    return false;
}

function sendDataByXHR(uploadData) {
    const xhr = new XMLHttpRequest();
    // 第三个参数设为 false,表示发起同步请求
    xhr.open(‘POST’, url, false);
    xhr.onreadystatechange = () => {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            finished = true;
        }
    };
    xhr.send(uploadData);
}

尾声

到此为止,我们就实现了一个用来收集上报性能数据的工具。所以我们再回到一开始的那个问题,我们应该什么时候开始做这件事?相信读者已经有了自己的判断。 如果文中有什么错误烦请指出,感谢!也欢迎留言讨论和交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值