前端性能优化一:性能指标

如果我们想要提高我们的前端性能,那么首先你得先只知道怎么测量你的前端性能。但是哪些前端的性能指标我们应该关注呢?
在Web1.0时代当我们讨论到前端性能指标用的最多的就是page load time。到了单页应用程序越来越多的时代,单一的page load time指标已经无法完全衡量前端性能了,因为一个单页应用程序page load time可能只触发了一次,但是用户会跟程序有很多的交互,每一个交互或者动作都需要有指标来衡量他的性能。

接下来就介绍几个比较重要的前端性能指标:

  • first paint (FP):这个指标标志着浏览器渲染第一个像素点的时间
  • first contentful paint (FCP):和FP标志着任意一个像素点被渲染的时间不同,FCP标志着浏览器渲染第一个内容元素的时间,这些内容元素可以是text,image,SVG,canvas.

这两个指标对用户来说都是很重要的,这两个指标可以认为我们程序正在告诉我们的用户:我们正在正确工作。

  • First meaningful paint (FMP):这个指标标志着首屏最重要的一块区域的渲染,通常是用户最关注的区域。比如视频网站的视频播放区域,搜索网站的第一个搜索结果区域,又或者是购物网站的照片首图。通常来说浏览器很难清楚的了解哪一块是对于网站首屏来说是最关键的,所以开发者自己来告诉浏览器是哪一块是关键区域是很有必要的。

这也是非常关键的一个指标,通常如果用户能够快速的看到最重要的一块区域被渲染完成,即使其他的区域都还没有被渲染用户可能也不会注意到。

  • long task:我们都知道浏览器是单线程的在响应用户的操作时通过在任务队列里面增加任务,然后一个个的执行的.这意味着如果我们有一个长任务需要使用较长的时间,那么队列中的其他任务就只能等待,响应用户的操作就会变慢,或者动画就会变卡顿。
  • Time to interactive (TTI):这个指标表示浏览器已经渲染完了我们首屏需要显示的内容并且已经准备好接受用户的交互信息了,也标志着程序是否可用。程序暂时无法响应用户的交互有下面几个原因:
    • 需要执行的javascript还没有执行完成。
    • 有长任务阻绝着主线程.无法给用户响应。
指标 介绍
first paint (FP)/first contentful paint (FCP) 程序是否正确的开始渲染
First meaningful paint (FMP) 用户最关注的的首屏内容显示
Time to interactive (TTI) 程序是否可用
long task 程序使用的体验(是否响应延迟,动画卡顿)

还有一些其他的指标比如 First Input Delay(首次接受用户响应的延迟时间) First CPU Idle(第一次CPU闲置的时间):这些指标都和上面的指标有着直接的关系。

用户体验

知道了这些指标,我们要把这些指标控制在什么样的时间才能给用户带来比较好的用户的体验呢,下面有这样一张表

时间 介绍
0到16ms 用户希望看的动画能够流畅,动画卡顿会带来非常差的用户体验,在浏览器上每秒钟渲染60帧动画就能够保持流畅,这大约就是16ms渲染一帧,这16ms包括了浏览器要渲染新的元素到页面上需要的时间,也就是说程序有大约10ms的时间可以进行操作。
0到100ms 在这个时间内响应用户的交互,用户会觉得响应是非常及时的
100到300ms 用户会感觉到有一些延迟
300到1000ms 当执行一些页面加载或者页面跳转的时候,在这个时间内是一个正常的加载跳转时间
1000ms或以上 超过1000ms(1秒),用户会对之前的操作渐渐失去耐心和注意力
10000ms或以上 当你的响应超过10秒,用户会感到烦躁,然后终止之前操作

上面的延迟时间取决于你使用什么样的网络和设备,比如你使用的电脑和wifi网络,用户在1000ms是一个比较现实的目标。但是当你设备是手机网络只有3G的时候在5000ms内加载才是更现实的目标。

响应时间:处理响应用户的操作在50ms以内

在大多数的时间里,用户在使用程序时大多数的时间都在等待网站响应他们的操作比如点击一个按钮,在文本框中输入内容,而不是等待网站加载。那么网站比较理想的响应用户时间是在100ms以内。

100ms?不是50ms吗?

我们的目标是在100ms以内响应用户的操作,那为什么处理用户的响应时间只有50ms?因为在我们接受到用户的输入时,可能会有别的任务正在执行.比如我们接受到用户在文本框中输入了一个A,这个时候浏览器正在执行别的任务,浏览器会把这个操作先加入到任务队列里,等浏览器执行完之前的任务才会去处理用户的响应。那么保守的估计为了让用户在100ms以内获得响应,我们的处理用户响应的执行时间就是50ms。
响应时间

动画:生产每一帧动画的时间在10ms左右

理论上来说只要没16ms渲染一帧,动画就会看起来是流畅的,但是浏览器大约需要6ms的时间来将每一帧渲染到画面上.因此产生每一帧动画的时间留给程序的大约就10ms左右。

主线程闲置时间越多越好

主线程能够有尽可能多的闲置时间,那么当产生用户交互时就可以立马给用户响应。当主线程闲置时,浏览器会有很多的内部程序需要执行,比如闲置GC等。

主要内容渲染完成且程序可交互时间在5秒之内

当页面加载缓慢,用户会失去耐心。网站的加载和响应速度直接影响用户的体验。

如何测试这些指标

使用测试工具或者网站

在真实的用户环境获得这些指标

在介绍如何在用户真实环境中或者这些指标之前先介绍一些API

获得FP/FCP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
//这里的name是为了区分'first-paint','first-contentful-paint'
const metricName = entry.name;
const time = Math.round(entry.startTime + entry.duration);
reportToServer({
eventCategory: 'Performance Metrics',
eventAction: metricName,
eventValue: time,
nonInteraction: true,
});
}
});
observer.observe({entryTypes: ['paint']});
根据你首屏最重要的元素获得FMP

之前已经介绍过对于浏览器来说很那准确的知道每个网站对重要的一块区域显示的时间,那么只有开发者自己能够准确的找到最重要的一块区域获得FMP,假设我们网站首屏最重要的元素是一个图片就可以这么写.

1
2
3
4
5
<img src="important.jpg" onload="performance.clearMarks('img displayed'); performance.mark('img displayed');">
<script>
performance.clearMarks("img displayed");
performance.mark("img displayed");
</script>
获得TTI

目前在PerformanceObserver中还没有办法获得TTI的接口,通过这个tti-polyfill可以知道这个tti

1
2
3
4
5
6
7
8
9
10
import ttiPolyfill from 'tti-polyfill.js';

ttiPolyfill.getFirstConsistentlyInteractive().then((tti) => {
reportToServer({
eventCategory: 'Performance Metrics',
eventAction: 'TTI',
eventValue: tti,
nonInteraction: true,
});
});
监视长任务

之前提到过,长任务可能会影响浏览器对用户响应速度或者造成动画的卡顿.那么能意识到长任务的存在并且把他缩短是很有必要的。(长任务API认为50ms以上任务的为长任务)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
reportToServer({
eventCategory: 'Performance Metrics',
eventAction: 'longtask',
eventValue: Math.round(entry.startTime + entry.duration),
//这里的长任务会包含一个attribute
//https://w3c.github.io/longtasks/#sec-TaskAttributionTiming
eventLabel: JSON.stringify(entry.attribution),
});
}
});

observer.observe({entryTypes: ['longtask']});
监视响应延迟

长任务会阻塞线程导致浏览器无法响应用户操作,之前也提到过如果能在100ms以内响应用户的操作就不会让用户觉得卡顿,那么如果能够监控到你关键交互的响应时间也是很有必要的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const submitBtn = document.querySelector('#submit');

submitBtn.addEventListener('click', (event) => {
const lag = performance.now() - event.timeStamp;
if (lag > 100) {
reportToServer({
eventCategory: 'Performance Metric'
eventAction: 'input-latency',
eventLabel: '#subscribe:click',
eventValue: Math.round(lag),
nonInteraction: true,
});
}
});
幸存者偏差

当我们的程序如果加载速度很慢(比如加载了过多的js),那么真实用户在网络环境不一致的情况下,有些响应过慢的用户可能早早的在加载完成前就已经退出网站,那么这里就会有一个幸存者偏差的问题.你监控的用户都是已经加载完成的用户。为了能够同时检测到退出的用户.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
///写在最头部
window.__trackAbandons = () => {
// 去掉事件监听那么这个方法只执行一次
document.removeEventListener('visibilitychange', window.__trackAbandons);
//因为我们还没有加载report js API 所以我们要让服务器提供一个post接口在接收这次请求
const ANALYTICS_URL = 'https://ANALYTICS_URL';
const TRACKING_ID = 'TRACKING_ID';
const CLIENT_ID = (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('&'));
};
//visibilitychange可以监听页面unload事件
//https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event
document.addEventListener('visibilitychange', window.__trackAbandons);

结论

性能对现在的程序越来越重要,那么在一个程序需要进行性能优化的时候.个人认为可以按照这样的一个顺序进行:

数据收集->讨论性能指标阀值->针对性能优化->数据重新验证优化结果

这里主要讨论了我们一些性能指标和收集性能指标的方法,后面会讨论如何针对每一块进行优化。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2018 - 2020 阿母工业前端组 All Rights Reserved.

UV : | PV :