前端性能优化--卡顿监控方案

2024-01-26 08:36:47 浏览数 (3)

卡顿大概是前端遇到的问题的最棘手的一个,尤其是卡顿产生的时候常常无法进行其他操作,甚至控制台也打开不了。

但是这活落到了咱们头上,老板说啥就得做啥。能本地复现的我们还能打开控制台,打个断点或者录制 Performance 来看看到底哪些地方占用较大的耗时。如果没法本地复现呢?

卡顿检测

首先,我们来看看可以怎么主动检测卡顿的出现。

卡顿,顾名思义则是代码执行产生长耗时,导致浏览器无法及时响应用户的操作。那么,我们可以基于不同的方案,来监测当前页面响应的延迟。

Worker 心跳方案

对应浏览器来说,由于 JavaScript 是单线程的设计,当卡顿发生的时候,往往是由于 JavaScript 在执行过长的逻辑,常见于大量数据的遍历操作,甚至是进入死循环。

利用这个特效,我们可以在页面打开的时候,就启动一个 Worker 线程,使用心跳的方式与主线程进行同步。假设我们希望能监测 1s 以上的卡顿,我们可以设置主线程每间隔 1s 向 Worker 发送心跳消息。(当然,线程通讯本身需要一些耗时,且 JavaScript 的计时器也未必是准时的,因此心跳需要给予一定的冗余范围)

由于页面发生卡顿的时候,主线程往往是忙碌状态,我们可以通过 Worker 里丢失心跳的时候进行上报,就能及时发现卡顿的产生。

但是其实 Worker 更多时候用于检测网页崩溃,用来检测卡顿的效果其实还不如使用window.requestAnimationFrame,因为线程通信的耗时和延迟导致该方案不大准确。

window.requestAnimationFrame 方案

前面前端性能优化--卡顿篇有简单提到一些卡顿的检测方案,市面上大多数的方案也是基于window.requestAnimationFrame方法来检测是否有卡顿出现。

window.requestAnimationFrame()会在浏览器下次重绘之前调用,常常用来更新动画。这是因为setTimeout/setInterval计时器只能保证将回调添加至浏览器的回调队列(宏任务)的时间,不能保证回调队列的运行时间,因此使用window.requestAnimationFrame会更合适。

通常来说,大多数电脑显示器的刷新频率是 60Hz,也就是说每秒钟window.requestAnimationFrame会被执行 60 次。因此可以使用window.requestAnimationFrame来监控卡顿,具体的方案会依赖于我们项目的要求。

比如,有些人会认为连续出现 3 个低于 20 的 FPS 即可认为网页存在卡顿,这种情况下我们则针对这个数值进行上报。

除此之外,假设我们认为页面中存在超过特定时间(比如 1s)的长耗时任务即存在明显卡顿,则我们可以判断两次window.requestAnimationFrame执行间超过一定时间,则发生了卡顿。

使用window.requestAnimationFrame监测卡顿需要注意的是,他是一个被十分频繁执行的代码,不应该处理过多的逻辑。

Long Tasks API 方案

熟悉前端性能优化的开发都知道,阻塞主线程达 50 毫秒或以上的任务会导致以下问题:

  • 可交互时间(TTI)延迟
  • 严重不稳定的交互行为 (轻击、单击、滚动、滚轮等) 延迟
  • 严重不稳定的事件回调延迟
  • 紊乱的动画和滚动

因此,W3C 推出 Long Tasks API。长任务(Long task)定义了任何连续不间断的且主 UI 线程繁忙 50 毫秒及以上的时间区间。比如以下常规场景:

  • 长耗时的事件回调
  • 代价高昂的回流和其他重绘
  • 浏览器在超过 50 毫秒的事件循环的相邻循环之间所做的工作

参考 Long Tasks API -- MDN

我们可以使用PerformanceObserver这样简单地获取到长任务:

代码语言:js复制
var observer = new PerformanceObserver(function (list) {
  var perfEntries = list.getEntries();
  for (var i = 0; i < perfEntries.length; i  ) {
    // 分析和上报关键卡顿信息
  }
});
// 注册长任务的观察
observer.observe({ entryTypes: ["longtask"] });

相比requestAnimationFrame,使用 Long Tasks API 可避免调用过于频繁的问题,并且performance timeline的任务优先级较低,会尽可能在空闲时进行,可避免影响页面其他任务的执行。但需要注意的是,该 API 还处于实验性阶段,兼容性还有待完善,而我们卡顿常常发生在版本较落后、性能较差的机器上,因此兜底方案也是十分需要的。

PerformanceObserver 卡顿检测

前面也提到,卡顿产生于用户操作后网页无法及时响应。根据这个原理,我们可以使用PerformanceObserver监听用户操作,检测是否产生卡顿:

代码语言:js复制
new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    const duration = entry.duration;

    const delay = entry.processingStart - entry.startTime;
    const eventHandlerTime = entry.processingEnd - entry.processingStart;

    console.log(`Total duration: ${duration}`);
    console.log(`Event delay: ${delay}`);
    console.log(`Event handler duration: ${eventHandlerTime}`);
  });
}).observe({type: 'event'});

这种方式的好处是避免频繁在requestAnimationFrame中执行任务,这也是官方鼓励开发者使用的方式,它避免了轮询,且被设计为低优先级任务,甚至可以从缓存中取出过往数据。

但该方式仅能发现卡顿,至于具体的定位还是得配合埋点和心跳进行会更有效,这部分会在下一篇文章中介绍。

查看Github有更多内容噢: https://github.com/godbasin

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

0 人点赞