狠狠地研究了下 PerformanceObserver API

这篇文章发布于 2023年08月27日,星期日,16:34,归类于 JS API。 阅读 7207 次, 今日 11 次 5 条评论

 

乌鸦南瓜封面图

一、Observer家族

没想到Observer家族还有个PerformanceObserver,这就有点匪夷所思,就是,之前我明明好好梳理过浏览器目前支持的Observer,但是都2023年了,突然出现个PerformanceObserver,就非常让人觉得不真实,之前怎么就没看到呢?

匪夷所思

MutationObserver可以用来观察DOM元素的变化,ResizeObserver可以用来观察DOM元素尺寸的变化,IntersectionObserver则可以观察元素和浏览器视窗相交的情况。

而本文要介绍的 PerformanceObserver 则可以对页面的各项性能指标进行观察。

二、关于性能 Performance API

Performance API是个庞大的家族,根本学不动的,看看下面这些(参考自MDN文档),知识密度也不是一般的高。

PerformanceEntryPerformanceEntryPerformanceMarkPerformanceMarkPerformanceMeasurePerformanceMeasurePerformancePaintTimingPerformancePaintTimingPerformanceResourceTimingPerformanceResourceTimingPerformanceNavigationTimingPerformanceNavigationTimingPerformanceElementTimingPerformanceElementTimingPerformanceLongTaskTimingPerformanceLongTaskTimingPerformanceEventTimingPerformanceEventTimingTaskAttributionTimingTaskAttributionTimingLargestContentfulPaintLargestContentfulPaintPerformancePerformancePerformanceObserverPerformanceObserverLayoutShiftLayoutShiftVisibilityStateEntryVisibilityStateEntry

PerformanceObserver是其中非常重要的一部分内容。

三、包含的性能指标

PerformanceObserver中包含如下表所示的一系列性能指标(可以通过 PerformanceObserver.supportedEntryTypes 返回,不同浏览器的支持情况可能会有差异):

指标 释义 Chrome logo Firefox logo Safari logo
back-forward-cache-restoration
element 元素加载时间,实例项是 PerformanceElementTiming 对象。
event 事件延迟,实例项是 PerformanceEventTiming 对象。
first-input 用户第一次与网站交互(即点击链接、点击按钮或使用自定义的JavaScript控件时)到浏览器实际能够响应该交互的时间,称之为First input delay – FID。
largest-contentful-paint 屏幕上触发的最大绘制元素,实例项是 LargestContentfulPaint 对象。
layout-shift 元素移动时候的布局稳定性,实例项是 LayoutShift对象。
long-animation-frame 长动画关键帧。
longtask 长任务实例,归属于 PerformanceLongTaskTiming 对象。
mark 用户自定义的性能标记。实例项是 PerformanceMark 对象。
measure 用户自定义的性能测量。实例项是 PerformanceMeasure 对象。
navigation 页面导航出去的时间,实例项是 PerformancePaintTiming 对象。
paint 页面加载时内容渲染的关键时刻(第一次绘制,第一次有内容的绘制,实例项是 PerformancePaintTiming 对象。
resource 页面中资源的时间信息,实例项是 PerformanceResourceTiming 对象。
taskattribution 长期任务有重大贡献的工作类型,实例项是 TaskAttributionTiming 对象。
soft-navigation
visibility-state 页面可见性状态更改的时间,即选项卡何时从前台更改为后台,反之亦然。实例项是 VisibilityStateEntry 对象。

PS:测试浏览器,Chrome 114,Firefox 116 和 Safari 16.5.2.

可以看到,基本上,每一种性能指标都有一个对应的专属对象类型。

其中,有些指标属于核心指标。

Web性能核心指标

  • LCP,Largest Contentful Paint,对应largest-contentful-paint类型,是加载性能指标。
  • FID,First Input Delay,对应first-input类型,是交互性能指标。
  • CLS,Cumulative Layout Shift,对应layout-shift类型,是视觉稳定性指标。

可以重点关注下,至于其他一些时间类型,及其对应的对象,咳咳……内容实在太多了,学不完,实在是学不完,所以,略过,等哪天真正需要了,可以再好好看一看。

四、paint类型与首次渲染时间

下面看下PerformanceObserver中 ‘paint’ 的运行结果。

看下面的代码和实例,在页面头部写入:

const observer = new PerformanceObserver(entryList => {
    for (const entry of entryList.getEntries()) {
        console.dir(entry);
    }
});
observer.observe({
  entryTypes: ['paint']
});

在Chrome浏览器下,会输出两个条目对象,如下截图所示:

Chrome下 paint 条目

分别是开始渲染和开始有内容(文字或图片所在DOM)渲染。

例如,有内容渲染的数据结构:

{
  "name":"first-contentful-paint",
  "entryType":"paint",
  "startTime":220.80000001192093,
  "duration":0
}

Firefox下则只有一个条目,因为支持的类型更少。

Firefox下的支持情况

其中可参考的指标就是 startTime,表示什么时候内容开始渲染,基本上,可以近似表示首屏内容呈现的时间。

根据我的测试,第一次进入的时候,时间都会比较长,200ms+,无论哪个浏览器都是,然后再刷新就很快了,都是100ms以内,我估计,是因为类似CSS文件这些资源被缓存了(外链CSS可以阻塞渲染),所以更快了。

眼见为实,您可以狠狠地点击这里:PerformanceObserver首屏渲染时间demo

//zxx: Chrome浏览器下paint有多个类型,我们将entryList.getEntries()替换成(例如)entryList.getEntriesByName('first-contentful-paint')

五、看看largest-contentful-paint的运行结果

虽然LCP是重要的性能指标,但是目前仅Chrome浏览器支持。

运行代码和’paint’类似,这里就不展示,感兴趣的可以猛击这里访问:largest-contentful-paint运行测试demo

输出的结果很奇怪。

在我本地(右上角广告图不加载的时候),最大内容渲染元素是<h1>标题,线上则是右上角的广告图。

以及,再输出的是pre元素,并且是在定时器触发内容更新后显示。

按照我的理解,最大渲染元素不应该是下面的色彩斑斓的底纹大色框吗?

后来我再想,大应该指的是内容,而不是渲染面积。

于是我又塞入了很多文字内容,结果还是一样。

最大内容元素

这个元素的选取规则实在令人费解。

于是我又仔细查看了下MDN文档,大致知道原因了。

The LargestContentfulPaint interface provides timing information about the largest image or text paint before user input on a web page.

需要是在页面可交互之前渲染的元素,并且要在视区范围之内(元素所有区域)。

基于这两点,我又重新修改了下页面内容,包括高度减小,内容增多,然后一刷新,哦吼,成了!

最大渲染内容

开心,又弄明白一个小知识。

六、过去不太方便获取的first-input指标

first-input指的是用户页面首次交互时间,我一开始还觉得是可交互时间,幸好测试了下。

const observer = new PerformanceObserver(entryList => {
    for (const entry of entryList.getEntries()) {
        console.dir(entry);
    }
});
observer.observe({
  entryTypes: ['first-input']
});

例如,上面的测试代码,在页面刷新后,是没有任何输出的。

只有当你点击页面,console语句才会执行。

这个API倒是可以用来统计页面是否被用户交互过,以及停留时间,也不知道可不可以用来识别是否是机器行为。

以后有机会试一下。

其输出结果示意:

起止时间示意

{
    "name": "pointerdown",
    "entryType": "first-input",
    "startTime": 2953.4000000953674,
    "duration": 0,
    "navigationId": 1,
    "processingStart": 2953.7000002861023,
    "processingEnd": 2953.7000002861023,
    "cancelable": true
}

其中startTime就是从页面开始可交互,到第一次交互的时间间隔,name是事件类型,点击是pointerdown,键盘访问是keydown。

我测试了下,在PC端,似乎只有点击和键盘方位才能触发,鼠标hover控件元素,以及鼠标滚轮滚动都是不会触发的。

眼见为实,您可以狠狠地点击这里:first-input首次交互时间测试demo

七、接下来是mark和measure

Web中的Performance中性能都是围绕时间线timeline展开的,。

从Chrome,Firefox等浏览器的性能分析工具可以看出:

时间线

而需要知道某一段时间线的性能,就需要知道这个时间线的起止点。

这个起止点的标记就叫做mark,期间的性能测量就是measure。

这两个动作对应两个专门的对象接口,前者是PerformanceMark,可以给任意位置命名和添加标记,后者是PerformanceMeasure,是两个标记之间的时间度量。

我们可以借助这两个对象精确测量某些性能指标。

一种方法是使用Performance API,还有就是使用PerformanceObserver。

这里通过一个例子演示下各自的使用。

假设页面上有两个元素,一个id是output,另外一个是footer,则测试代码如下(使用定时器模拟时间间隔):

// mark标记
performance.mark('test-start', {
    startTime: 0,
      detail: { htmlElement: 'output' }
});

setTimeout(() => {
    performance.mark('test-end', {
        startTime: 360,
        detail: { htmlElement: 'footer' }
    });    
    
    const testMeasure = performance.measure(
      "test-start",
      "test-end"
    );
    
    console.dir(testMeasure);
}, 360);

const observer = new PerformanceObserver(entryList => {
    entryList.getEntries().forEach((entry) => {
        var logMark = '';
        var logMeasure = '';
        if (entry.entryType === 'mark') {
              logMark = `${entry.name}的startTime是: ${entry.startTime}`;
              console.log(logMark);
        }
        if (entry.entryType === 'measure') {
              console.log(logMeasure = `${entry.name}的duration时间是: ${entry.duration}`);
        }
    });
});
observer.observe({
    entryTypes: ['mark', 'measure']
});

此时,就可以看到执行开始时间,和时间间隔等信息了。

渲染时间查看

此时的 duration 应该就是两个DOM元素之间的渲染时间了吧。

眼见为实,您可以狠狠地点击这里:mark和measure类型测试demo

相比与Date.now()或者使用performance.now()方法记录时间,使用mark进行标记的位置还可以和开发者工具的性能面板配合使用,方便我看查看详情,有助于我们定位问题,以及理解Web页面的加载。

例如,上面的demo页面中,’test-start’这个标记就可以在事件时间线中看到。

时间线截图

八、好了,打住,该结束了

经过这一波阅读,测试和撰写,我已经大致搞清楚PerformanceObserver的花花草草了……嗯嗯嗯……是这样的,等哪一天遇到页面卡顿,可以用这个排查看看。

至于性能统计与上报也是个不常见的需求。

所以,了解下就好了。

平常那些项目,只要正常开发,以目前的浏览器性能,都会很流畅的。

另外,还是要亲自测试,只靠看文档,很多东西是看不出来的,并且文档很多内容都是缺失的。

OK,马上9月了,是时候拧紧发条了。

好了,内容已经够多的了,就不再多扯淡了。

感谢您的阅读,行为仓促,如有错误,欢迎指正。

比心~

💓 💗 💖 💘 💝

(本篇完)

分享到:


发表评论(目前5 条评论)

  1. cmqq说道:

    你的文章深度和广度,以及文案逻辑性真的都很有限

  2. codehz说道:

    back-forward-cache这个东西我觉得挺有用的,在前进后退的时候能保留之前的js运行状态,对传统多页网页的场景很有优势

  3. Leyla说道:

    在我电脑上试了一下,’first-input’ 这个指标在输入点击普通键位的时候也可以触发。我是mac电脑、chrome 116

  4. 猪精的饲养员说道:

    第一条评论,哈哈