从web-vitals到PerformanceObserver

2,661 阅读2分钟

Essential metrics for a healthy site.

web-vitals是谷歌新出的性能检测工具,从 github 上面的定义可以看到,对网站的主要几个性能指标进行检测上报,类似 LCP、FID、CLS 等,但是在深入讲 web-vitals 之前,先暂时讲下PerformanceObserver,主要是这货在mdn上的描述真的不是很详细,而 web-vitals 主要用到了 PerformanceObserver 进行指标的获取

image

关于 PerformanceObserver 的细节

PerformanceObserver 的主要用法,可以看 mdn 上面的描述,但是在看 web-vitals 的时候,看到observer.observe({type: "paint", buffered: true});,可以看到多了一个 bufferd 参数,同时在 FID 指标的获取的时候,除了在回调里面上报之外,还有一个 takeRecords 的回收,同样 mdn 的描述也模棱两可,只是说清空性能指标条目,没说具体。带着疑问,找到了 chrome 的文档说明。

关于 bufferd 为 true

bufferd 的描述

image
简单地说,就是在我们创建 PerformanceObserver 之前,可能有些 entry 已经发生了,这中间有时间间隔,导致我们观察到的不是正确的。举个例子来说。
image
假设我们想之前现在访问这个网页的 entryType 为 paint 的 entry ,但是我们打开控制台的时候,网页大概率已经加载完毕了,肯定观察不到 entry 了,bufferd 就出场了,bufferd 能拿到我们PerformanceObserver 创建之前的 entry,如下所示,所以我们就可以 first-paint 和 first-contentful-paint 的 entry 了
image

关于 takeRecords

takeRecords 文档可以看出,返回 buffer 中的 拷贝,以及清空 buffer,也是 FID 中 需要做的,把 buffer 中的 entry 清空。

The takeRecords method returns a copy of the performance entries in the PerformanceObserver’s buffer, and also clears this buffer.

举个例子来说,如果我们的 FID 中, 在我们还没来得及用 buffer 中的去触发回调时,页面就隐藏了,那么我们需要拿 buffer 中的数据去计算,而如果 buffer 被回调消耗了,那么 takeRecords 中的 buffer 为空。

image
以 first-paint 为例子,在创建 PerformanceObserver 之前,我已经手动触发了 input,所以 buffer 被回调消耗掉,所以是第五行,而如果上面的代码我把 var records = observer.takeRecords() 的注释放开,刷新页面重复刚才的操作,buffer 是 被 takeRecords 消费掉的,这就是 PerformanceObserver 中 关于 buffer 的主要疑惑点,下面会进入正题进行 web-vitals 的解析
image

关于 web-vitals (待补全)

web-vitals 一共有五个性能指标,下面会分别讲到

FCP

FCP 全称 first-contentful-paint,下面我们可以看到我们的指标上传时, 只针对页面 first-contentful-paint 发生的时候页面没有隐藏起来,那如果页面一直是隐藏状态,这时 firstHidden.timeStamp 默认为 0 ,那 FCP 指标就永远不会上报

export const getFCP = (onReport: ReportHandler) => {
  const metric = initMetric('FCP');
  const firstHidden = getFirstHidden();

  const po = observe('paint', (entry: PerformanceEntry) => {
    if (entry.name === 'first-contentful-paint') {
      // Only report if the page wasn't hidden prior to the first paint.
      if (entry.startTime < firstHidden.timeStamp) {
        metric.value = entry.startTime;
        metric.isFinal = true;
        metric.entries.push(entry);
        report();
      }
    }
  });

  const report = bindReporter(onReport, metric, po);
};