如何评价前端性能好坏?
如何评价前端性能好坏?
加载时间是我们用于评价前端性能好坏的一个参考数据,但是加载时间与用户使用的手机类型以及当前网络状况有关,除此之外,即使页面加载完成用户也可能无法操作,所以仅仅用一个加载时间是不能描述前端性能的好坏,我们需要一套完整的性能评价体系。
此前,我们有两个主要的事件来测量性能:
- DomContentLoaded:页面加载时触发,但脚本刚刚开始执行
- load:在页面完全加载后触发,此时用户已经可以使用页面和应用

但是,如果在网络环境较差或者脚本执行时间太长的时候,我们从DCL到load会经历一段很长很长的时间,并且load事件触发太晚,就无法分析出页面的性能瓶颈,所以我们需要建立一个从页面开始加载到加载完成的评价体系,记录用户整个感知过程。
渐进式网页指标


首次绘制(FP): 标记浏览器渲染任何在视觉上不同于导航前屏幕内容之内容的时间点
首次内容绘制(FCP):标记的是浏览器渲染来自 DOM 第一位内容的时间点,该内容可能是文本、图像、SVG 甚至 <canvas> 元素。
首次有效绘制(FMP)/主角元素计时:标记的是页面主要内容绘制的时间点,例如视频应用的视频组件、天气应用的天气信息、新闻应用中的新闻条目。
可交互时间(TTI):页面渲染完成,能可靠的响应用户的操作的时间点。(这个时间不仅取决于之前的加载时间,还取决于当前是否有长任务阻塞主线程)
浏览器页面加载流程
先谈谈浏览器的页面大致的加载流程:
- 浏览器输入url,浏览器发送请求到服务器,服务器发送HTML给浏览器。
- 浏览器下载HTML然后从上往下解析
- 如果在解析HTML过程中遇到CSS、JS外链,会执行下列操作
- Send Request:向这个外链对应的服务器发送请求
- Receive Response:接受响应,表示可以开始从网络接受数据
- Receive Data:开始接受数据
- Finish Loding:表示完成数据的下载
- Parse Stylesheet/Evaluate:解析CSS或执行JS
- 所有的CSS下载完成后进行parse,构建CSSOM
- DOM和CSSOM合成一个Render tree
- 根据Render tree的内容计算处各个节点在网页中的大小和位置(Layout)
- 根据Layout绘制内容在浏览器上(Paint)
如何统计上述的指标
通过performance接口,我们可以获得当前页面与性能相关的信息;然后通过navogator.sendBeacon或Google Analytics向服务器发送与性能相关的信息。
那么,具体的性能指标如何统计呢?👇👇👇
首次绘制(First Paint,FP)
这个指标表示页面绘制的时间点,也就是用户第一次看到“白屏”的时间,标志“It is happening?”
首次绘制包括了任何用户自定义的背景绘制,它是首先将像素绘制到屏幕的时刻。
什么时候触发FP事件呢?FP事件是在图层进行绘制的时候触发,而不是文本、图片、SVG等元素出现的时候(这个时候触发的是FCP事件)
怎么统计这个FP时间呢?
window.perfomance.getEntriesByType(‘paint’),这会返回FP、FCP发生的时间点。具体参考https://developer.mozilla.org/zh-CN/docs/Web/API/Performance
通过使用 Google Analytics,可跟踪首次绘制时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31<head>
<!-- Add the async Google Analytics snippet first. -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<!-- Register the PerformanceObserver to track paint timing. -->
<script>
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// `name` will be either 'first-paint' or 'first-contentful-paint'.
const metricName = entry.name;
const time = Math.round(entry.startTime + entry.duration);
ga('send', 'event', {
eventCategory:'Performance Metrics',
eventAction: metricName,
eventValue: time,
nonInteraction: true,
});
}
});
observer.observe({entryTypes: ['paint']});
</script>
<!-- Include any stylesheets after creating the PerformanceObserver. -->
<link rel="stylesheet" href="...">
</head>首次内容绘制(First Content Paint,FCP)
当用户首次看见一些元素(文本、图片、SVG等),非”白屏“的时刻就是FCP,这个标志“it is running?”
统计的方法与上述FP的统计方法类似,Google的Analysis监听的paint实际有两种类型,‘first paint’和‘first content pain’。
FCP在文本、图片、SVG等绘制的时候就触发了,所以跟FP可以相差几毫秒~几秒的时间。
首次有效绘制(First Meaningful Paint,FMP)
FMP就是页面主要内容呈现的时刻,目前没有标准化的FMP定义,所以很以通用的方式确定“有效”对于所有页面意味着什么。
所以,我们怎么统计FMP呢?它的统计在页面发生了重大的布局变化之后;LayoutAnalyzer会统计所有的布局变化,当布局变化最大时,这个时刻就是FMP。
如果主要内容渲染太慢,我们可以在样式、字体、脚本、图片上去思考,因为这些元素有很大可能阻塞了FMP。
可交互时间(Time To Interactive,TTI)
与 FMP 相同,很难规范化适用于所有网页的 TTI 指标定义,但我们也可以使用Google Analytics来跟踪应用TTI。
TTI发生在什么时候?FMP && DOMContentLoader事件触发 && 页面视觉加载85%
TTI又可以分为两个指标,TTFI(首次交互时间)、TTCI(首次持续交互时间)
TTFI:
在FMP发生后应该有3s的quiet window,这个时间足够说明页面对用户是可交互的,但是可能会有长任务在这个quiet window期间或之后开始执行,它们可以被忽略。
TTCI:
从追终线的尾部开始看,页面加载后有5s的quiet并且没有长任务需要执行,得到了一段quiet window。quiet window之后到第一个长任务之前的时间就是TTCI。
影响上面某些指标的因素
FP/FCP:“白屏”,从文档的
<head>中移除任何阻塞渲染的脚本或样式表,可以减少首次绘制和首次内容绘制前的等待时间。FCP:首次内容绘制耗时太长,直观上就是“白板时间太长”,影响因素就是网络连接存在性能问题、资源(例如index.html)太大传输耗时。FMP/TTI:如果主要内容很久都没展示出来,那么很有可能是图片、样式、字体、JavaScript有较高的加载优先级,阻塞了FMP。确定页面上最关键的界面元素(主角元素)之后,您应确保初始脚本加载仅包含渲染这些元素并使其可交互所需的代码。
分析关键渲染路径
优化关键渲染路径就是让刘篮球尽可能快地绘制网页,减少用户看到“白屏的时间”,从FP到FCP;所以整个时间就包括了获取资源(CSS、JS、HTML)以及浏览器处理对应的文件。
关键资源:可能阻止网页首次渲染的资源
关键路径长度:获取所有关键资源所需的往返次数或总时间
关键字节:实现网页首次渲染所需的总字节数,它是所有关键资源传送文件大小的总和。
HTML、Image
对于HTML,浏览器下载HTML文件,当HTML内容可用时,浏览器就会解析HTML,生成token,进而构建DOM树,DOM树构建完成同时意味着DOMContentLoaded事件触发;获取图片并不会影响DOMContentLoaded事件的触发,所以图片不会影响到首次绘制。

关键资源(HTML),关键路径长度(最少1次),关键字节(5KB)
HTML、CSS
加载解析HTML,然后获取CSS文件,解析为CSSOM,生成渲染树,绘制页面。

关键资源(HTML、CSS),关键路径长度(最少2次),关键字节(9KB)
CSS、JavaScript、HTML
1
2
3
4
5
6
7
8
9
10
11
12<html>
<head>
<title>Critical Path: Measure Script</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
<script src="timing.js"></script>
</body>
</html>除了获取HTML、解析为DOM树,我们还会去加载CSS文件构建CSSOM,然后通过DOM和CSSOM构建渲染树,按道理来说,DCL也应该在DOM树生成后就触发,但是浏览器在下载解析CSS文件之前会阻止DCL事件,这是因为JS可能会去查询CSSOM。即使将脚本更改为内联,也无济于事。
所以,只要浏览器遇到script标记就会阻止,并等到CSSOM构建完毕。

关键资源(HTML、CSS、JS),关键路径(最少2次,获取css、js时为并行),关键字节(11KB)
async,异步脚本的用处
1
2
3
4
5
6
7
8
9
10
11
12<html>
<head>
<title>Critical Path: Measure Script</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
<script src="timing.js" async></script>
</body>
</html>这种情况下,解析HTML之后不久就会触发DCL事件,浏览器知道不需要阻止JS,不必等到CSS文件加载解析后再执行JS。

关键资源(HTML、CSS),关键路径(最少2次),关键字节(9KB)
优化
上面提到,有三种因素(关键资源、关键路径、关键字节)影响着首次渲染,所以优化也应该从这三点入手。
- 减少关键资源的数量:删除、延迟、标记异步等
- 优化关键字节数以缩短下载时间(压缩)
- 优化其余关键资源的加载顺序(尽早下载所有的关键资产,缩短关键路径长度)
业界一些常见的优化策略
Google PageSpeed Insights Rules
1. **消除阻塞渲染的JavaScript和CSS**(最大限度减少网页上关键资源的数量并尽可能消除这些资源,减少下载关键字节数,优化关键路径长度)
2. **优化JavaScript的使用**(JavaScript资源会阻塞解析器,所以将其标记为async或通过专门的JavaScript代码进行添加。**阻塞解析器的JavaScript强制浏览器等待CSSOM并暂停DOM的构建,继而大大延迟首次渲染的时间**)
3. **延迟解析JavaScript**(延迟加载对构建首次渲染的可见内容无关紧要的脚本)
4. **避免运行时间长的JavaScript**
5. **优化CSS的使用**(CSS置于文档head标签内、避免使用CSS import、内联阻塞渲染的CSS)
Yahoo Best Practices for Speeding Up Your Web Site
- 减少http请求
- 使用CDN
- 利用浏览器缓存
- 压缩页面元素
- 样式表放在head
- JavaScript文件放在底部
- 避免CSS表达式
- JS、CSS放在外部文件中
- 减少CDN查询
- 压缩JavaScript
- 避免重定向
现代前端应用的优化策略
前端资源文件离线化
页面组件化并按需加载
预渲染提升感官性能
RAIL

响应:在100ms以内响应
在用户注意到滞后之前有100ms的时间可以响应用户输入。
动画:每10ms生成一帧
尽管每秒生成60帧,一帧16.6ms,但是每帧只有10ms来执行代码,其余的时间用于将新帧绘制到屏幕上。
空闲:最大程度增加空闲时间
利用空闲时间完成推迟的工作,换句话说,就是让主线程在UI线程和JS线程中切换。
加载:在1000ms以内呈现内容
在 1 秒钟内加载您的网站。
总结
