frontend9 / fe9-library

九部知识库
1.94k stars 138 forks source link

前端性能量化标准 #7

Open shaozj opened 6 years ago

shaozj commented 6 years ago

前端性能量化标准

我们经常能看到大量介绍前端如何进行性能优化的文章。然而很多文章只介绍了如何优化性能,却未能给出一个可计算,可采集的性能量化标准。甚至看到一些文章,在介绍自己做了优化后的性能时,提到页面加载速度提升了多少多少,但是当你去问他你怎么测量性能的时,却不能给出一个科学的、通用的方法。
其实,在进行性能优化前,首先需要确定性能衡量标准。前端性能大致分为两块,页面加载性能和页面渲染性能。页面加载性能指的是我们通常所说的首屏加载性能。页面渲染性能指的是用户在操作页面时页面是否能流畅运行。滚动应与手指的滑动一样快,并且动画和交互应如丝般顺滑。这两种页面性能,都需要有可量化的衡量标准。
本文参考了谷歌提出的性能衡量方式。首先确定以用户体验为中心的性能衡量标准。然后,针对这些性能标准,制定采集性能数据的方法,以及性能数据分析方法。最后,结合性能量化标准,提出优化性能的方法。

页面加载性能

性能度量标准

下表是与页面加载性能相关的用户体验。

用户体验 描述
它在发生吗? 网页浏览顺利开始了吗?服务端有响应吗?
它是否有用? 用户是否能看到足够的内容?
它是否可用? 用户是否可以和页面交互,还是页面仍在忙于加载?
它是否令人愉快的? 交互是否流程和自然,没有卡段或闪烁?

与用户体验相关,制定以下度量标准:

在用户设备中测量性能

下面是一段开发者经常用来 hack 检查页面中长任务的代码:

// detect long tasks hack

(function detectLongFrame() {
  var lastFrameTime = Date.now();
  requestAnimationFrame(function() {
    var currentFrameTime = Date.now();

    if (currentFrameTime - lastFrameTime > 50) {
      // Report long frame here...
    }

    detectLongFrame(currentFrameTime);
  });
}());    

hack 方式存在一些副作用:

性能测量的代码最重要的准则是它不该使性能变差。

本地开发时性能的测量

LighthouseWeb Page Test 为我们本地开发提供了非常好的性能测试工具,而且对于我们前面提到的各项测量标准都有较好的支持。但是,这些工具不能在用户的机器上运行,所以它们不能反映用户真实的用户体验。

用户设备中性能的测量

幸运的是,随着新 API 的推出,我们可以再用户设备上测量这些性能而不需要付出用可能使性能变差的 hack 的方式。
这些新的 API 是 PerformanceObserver, PerformanceEntry, 以及 DOMHighResTimeStamp

// 性能度量结果对象数组
const metrics = [];

if ('PerformanceLongTaskTiming' in window) {
  const observer = new PerformanceObserver(list => {
    for (const entry of list.getEntries()) {
      const metricName = entry.name;
      const time = Math.round(entry.startTime + entry.duration);
      metrics.push({
        eventCategory: 'Performance Metrics',
        eventAction: metricName,
        eventValue: time,
        nonInteraction: true
      });
    }
  });
  observer.observe({ entryTypes: ['paint'] });
}

标准中并未定义 FMP,我们需要根据页面的实际情况来定 FMP。一个较好的方式是测量页面关键元素渲染的时间。参考文章 User Timing and Custom Metrics

测量 css 加载完成时间:

<link rel="stylesheet" href="/sheet1.css">
<link rel="stylesheet" href="/sheet4.css">
<script>
performance.mark("stylesheets done blocking");
</script>

测量关键图片加载完成时间:

<img src="hero.jpg" onload="performance.clearMarks('img displayed'); performance.mark('img displayed');">
<script>
performance.clearMarks("img displayed");
performance.mark("img displayed");
</script>

测量文字类元素加载完成时间:

<p>This is the call to action text element.</p>
<script>
performance.mark("text displayed");
</script>

计算加载时间:

function measurePerf() {
  var perfEntries = performance.getEntriesByType("mark");
  for (var i = 0; i < perfEntries.length; i++) {
    console.log("Name: " + perfEntries[i].name +
      " Entry Type: " + perfEntries[i].entryType +
      " Start Time: " + perfEntries[i].startTime +
      " Duration: "   + perfEntries[i].duration  + "\n");
  }
}

采用谷歌提供的 tti-polyfill

import ttiPolyfill from './path/to/tti-polyfill.js';

ttiPolyfill.getFirstConsistentlyInteractive().then((tti) => {
  ga('send', 'event', {
    eventCategory: 'Performance Metrics',
    eventAction: 'TTI',
    eventValue: tti,
    nonInteraction: true,
  });
});

TTI 标准定义文档

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    ga('send', 'event', {
      eventCategory: 'Performance Metrics',
      eventAction: 'longtask',
      eventValue: Math.round(entry.startTime + entry.duration),
      eventLabel: JSON.stringify(entry.attribution),
    });
  }
});

observer.observe({entryTypes: ['longtask']});

数据分析

当我们收集了用户侧的性能数据,我们需要把这些数据用起来。真实用户性能数据是十分有用的,原因包括:

下面是一个用图表来分析数据的例子:

这个例子展示了 PC 端和移动端的 TTI 分布。可以看到移动端的 TTI 普遍长于 PC 端。

PC 端:

比例 TTI(seconds)
50% 2.3
75% 4.7
90% 8.3

移动端:

比例 TTI(seconds)
50% 3.9
75% 8.0
90% 12.6

对这些图表使的分析得我们能快速地了解到真实用户的体验。从上面的表格我们能看到,10% 的移动端用户在 12s 后才能开始页面交互!

性能是如何影响商业的

利用用户侧性能数据,我们可以分析性能是如何影响商业的。例如,如果你想分析目标达成率或者电商转化率:

如果证明他们之间是有关联的,那么这就很容易阐述性能对业务的重要性,且性能是应该被优化的。

放弃加载

我们知道,如果页面加载时间过长,用户就会经常选择放弃。不幸的是,这就意味着我们所有采集到的性能数据存在着幸存者偏差——性能数据不包括那些因为放弃加载页面的用户(一般都是因为加载时间过长)。
统计用户放弃加载会比较麻烦,因为一般我们将埋点脚本放在较后加载。用户放弃加载页面时,可能我们的埋点脚本还未加载。但是谷歌数据分析服务提供了Measurement Protocol 。利用它可以进行数据上报:

<script>
window.__trackAbandons = () => {
  // Remove the listener so it only runs once.
  document.removeEventListener('visibilitychange', window.__trackAbandons);
  const ANALYTICS_URL = 'https://www.google-analytics.com/collect';
  const GA_COOKIE = document.cookie.replace(
    /(?:(?:^|.*;)\s*_ga\s*\=\s*(?:\w+\.\d\.)([^;]*).*$)|^.*$/, '$1');
  const TRACKING_ID = 'UA-XXXXX-Y';
  const CLIENT_ID =  GA_COOKIE || (Math.random() * Math.pow(2, 52));

  // Send the data to Google Analytics via the Measurement Protocol.
  navigator.sendBeacon && navigator.sendBeacon(ANALYTICS_URL, [
    'v=1', 't=event', 'ec=Load', 'ea=abandon', 'ni=1',
    'dl=' + encodeURIComponent(location.href),
    'dt=' + encodeURIComponent(document.title),
    'tid=' + TRACKING_ID,
    'cid=' + CLIENT_ID,
    'ev=' + Math.round(performance.now()),
  ].join('&'));
};
document.addEventListener('visibilitychange', window.__trackAbandons);
</script>

需要注意的是,在页面加载完成后,我们要移除监听,因为此时监听用户放弃加载已经没有意义,因为已经加载完成。

document.removeEventListener('visibilitychange', window.__trackAbandons);

优化页面加载性能

我们定义了以用户为中心的性能量化标准,就是为了指导我们优化性能。
最简单的优化性能的方式是减少需要传输给客户端的 js 代码。但是如果我们已经无法缩小 js 代码体积,那就需要思考如何传输我们的 js 代码。

优化 FP/FCP

优化 FMP/TTI

防止 long tasks

if ('requestIdleCallback' in window) {
  // Use requestIdleCallback to schedule work.
} else {
  // Do what you’d do today.
}

页面渲染性能

TODO:

其他性能测量方式

Navigation Timing

load 事件与 DOMContentLoaded 事件

顺序是:DOMContentLoaded -> load。
单纯地用 load 事件或者 DOMContentLoaded 事件来衡量页面性能,并不能很好地反馈出站在用户角度的页面性能。

nelling commented 6 years ago

写的很通透,基本点大多涵盖到了,好文

mperfect commented 6 years ago

很详细。对首屏性能进行度量,并根据浏览器渲染流程进行优化,很不错的总结。

jie-cao commented 6 years ago

膜拜前端大神!前端性能平均一直是困扰大家的问题。文章写的很全面,可以根据这个总结,推广出去,作为前端性能量化的标准。

xxs665 commented 5 years ago

醍醐灌顶

wingtao commented 2 years ago

ssr不能优化 fcp 吧,理论上反而会比 csr更差些