前言
一. 性能指标介绍
1.1 单一指标介绍
1.2 指标计算
- Redirect(重定向耗时)
- AppCache(应用程序缓存的DNS解析)
- DNS(DNS解析耗时)
- TCP(TCP连接耗时)
- TTFB(请求响应耗时)
- Trans(内容传输耗时)
- DOM(DOM解析耗时)
1.3 FP(first-paint) 和 FCP(first-contentful-paint)
1.4 LCP(Largest Contentful Paint)
1.5 LongTask长任务统计
以用户为中心的性能指标
- First Paint 首次绘制(FP)
- First contentful paint 首次内容绘制(FCP)
- Largest contentful paint 最大内容绘制(LCP)
- First input delay 首次输入延迟(FID)
- Time to Interactive 可交互时间(TTI)
- Total blocking time 总阻塞时间 (TBT)
- Cumulative layout shift 累积布局偏移(CLS)
三大核心指标
- Largest contentful paint 最大内容绘制(LCP)
- First input delay 首次输入延迟(FID)
- Cumulative layout shift 累积布局偏移(CLS)
如果LCP<2.5s ,FID<100ms,CLS<0.1,说明页面性能很好了
谷歌用LCP衡量加载性能,FID衡量交互性能,CLS衡量视觉稳定性
为什么用LCP而不用FCP衡量加载性能?
- 因为部分页面会有loading,会影响FCP的计算时间
二. 性能指标计算测试
2.1 衡量网络请求响应时间的指标
2.2 衡量页面加载速度的指标
2.3 TTI(Time to Interactive)衡量页面可交互性的指标
2.4 TBT(Total Blocking Time)
2.5 总结
前言
利用LightHouse进行合理的页面性能优化 这篇文章主要讲解了如何使用Lighthouse。 这里把相关图片再展示一下:
我们可以看到Lighthouse计算的时候,会根据这几个维度的指标来计算总分。那么本篇文章,就主要讲解下前端性能监控相关的重要指标含义和计算方式。
一. 性能指标介绍
在介绍指标之前,我们首先应当知道这些数据可以从哪里获取。JS里面,有一个 performance 对象,它是专门用来用于性能监控的对象,内置了一些前端需要的性能参数。
我们随便打开一个浏览器,在终端控制台输入以下内容:
performance.getEntriesByType('navigation')
如图:
1.1 单一指标介绍
navigationStart:导航开始的时间,即浏览器开始获取页面的时间。
redirectCount:重定向次数,表示在导航过程中发生的重定向次数。
type:导航类型,可能的取值有:
navigate:常规导航,例如用户点击链接或输入URL进行的导航。
reload:页面重新加载。
back_forward:通过浏览器的前进或后退按钮导航。
unloadEventStart:前一个页面的unload事件开始的时间。
unloadEventEnd:前一个页面的unload事件结束的时间。
redirectStart:重定向开始的时间。
redirectEnd:重定向结束的时间。
fetchStart:浏览器开始获取页面资源的时间。
domainLookupStart:域名解析开始的时间。
domainLookupEnd:域名解析结束的时间。
connectStart:建立与服务器连接开始的时间。
connectEnd:建立与服务器连接结束的时间。
secureConnectionStart:安全连接开始的时间,如果不是安全连接,则该值为0。
requestStart:向服务器发送请求的时间。
responseStart:接收到服务器响应的时间。
responseEnd:接收到服务器响应并且所有资源都已接收完成的时间。
domLoading:开始解析文档的时间。
domInteractive:文档解析完成并且所有子资源(例如图片、样式表等)也已加载完成的时间。
domContentLoadedEventStart:DOMContentLoaded事件开始的时间,表示HTML文档解析完成并且所有脚本文件已下载完成。
domContentLoadedEventEnd:DOMContentLoaded事件结束的时间,表示所有脚本文件已执行完成。
domComplete:文档和所有子资源(例如图片、样式表等)都已完成加载的时间。
loadEventStart:load事件开始的时间,表示所有资源(包括图片、样式表、脚本文件等)都已加载完成。
loadEventEnd:load事件结束的时间,表示所有资源(包括图片、样式表、脚本文件等)都已执行完成。
1.2 指标计算
我们看下图
我们从上图出发,分别对各个阶段进行计算,我们说下几个比较重要的阶段,按照从左往右的顺序。
① Redirect(重定向耗时)
表示从重定向开始(redirectStart)到重定向结束的时间(redirectEnd)的时间间隔,它反映了浏览器在这段时间内完成了重定向的过程:
const redirectTime = redirectEnd - redirectStart
② AppCache(应用程序缓存的DNS解析)
这一部分也是在进行DNS解析,在使用AppCache(应用程序缓存)的情况下,浏览器会在加载页面时检查缓存中是否存在相应的资源,并根据需要更新缓存:
const appcacheTime = domainLookupStart - fetchStart
③ DNS(DNS解析耗时)
DNS解析耗时:在浏览器加载网页时,当需要与服务器建立连接时,浏览器会首先进行DNS解析,将域名转换为对应的IP地址。DNS解析的过程包括向DNS服务器发送查询请求、等待DNS服务器响应以及获取到IP地址。
dns = domainLookupEnd - domainLookupStart
④ TCP(TCP连接耗时)
TCP连接耗时:在浏览器加载网页时,当浏览器需要与服务器建立连接时,它会向服务器发送请求,并等待服务器响应。建立连接的过程包括TCP握手、SSL握手等。
tcp = connectEnd - connectStart
其中还有建立SSL连接的时间,包括在TCP耗时里面。
ssl = connectEnd - secureConnectionStart
⑤ TTFB(请求响应耗时)
请求耗时:从发送请求到接收到服务器响应的第一个字节所花费的时间。
ttfb = responseStart - requestStart
⑥ Trans(内容传输耗时)
当浏览器发送请求后,服务器会返回相应的响应,这个差值就是衡量浏览器接收服务器响应的耗时。
trans = responseEnd - responseStart
⑦ DOM(DOM解析耗时)
DOM这一块比较复杂,实际上还能分成3个小DOM阶段。
阶段一(注意,上图中并没有显式地展示出来):解析DOM阶段。
const dom1 = domInteractive - responseEnd
阶段二:文档解析完成,html、js解析完成,css、图片加载完成。即加载DOM阶段。
const dom2 = domComplete-domInteractive
阶段二当中还可以分出一小个阶段:代表从开始加载DOM内容到DOM内容加载完成的时间间隔。
const domLoaded = domContentLoadedEventEnd - domContentLoadedEventStart
1.3 FP(first-paint) 和 FCP(first-contentful-paint)
FP(first-paint)和FCP(first-contentful-paint)
FP指的是浏览器首次将像素渲染到屏幕上的时间点,即页面开始渲染的时间点。通常情况下,FP是指浏览器首次绘制任何可见的内容,包括背景色、文字、图片等,但不包括用户界面的控件,比如滚动条、按钮等。
FCP指的是浏览器首次将页面的有意义的内容渲染到屏幕上的时间点,即页面开始呈现有意义的内容的时间点。有意义的内容可以是文本、图片、视频等,但不包括背景色、边框等无意义的内容。
一般情况下,两者基本上没有什么区别,来说下两者的获取方式:
const fp = performance.getEntriesByName('first-paint')[0].startTime
const fcp = performance.getEntriesByName('first-contentful-paint')[0].startTime
后面我们都只说FCP。
1.4 LCP(Largest Contentful Paint)
LCP:Largest Contentful Paint :它表示在页面加载过程中,最大的可见内容元素(例如图片、视频、文本块等)加载完成并呈现在屏幕上的时间点。,是测量加载速度感知的重要指标之一。
获取方式,我们主要通过Performance 来进行监听:
new PerformanceObserver((entryList) => {
var maxSize = 0;
var renderTime = 0;
for (var entry of entryList.getEntries()) {
// 渲染的内容看最大值
if(entry.size > maxSize){
maxSize = entry.size;
renderTime = entry.startTime;
}
}
console.log('LCP', renderTime)
}).observe({type: 'largest-contentful-paint', buffered: true});
1.5 LongTask长任务统计
LongTask(长任务)是指在JavaScript主线程上执行时间超过50毫秒的任务。这些任务可能是复杂的计算、大量数据处理、DOM操作或其他耗时的操作。
我们可以通过以下方式来获取:
new PerformanceObserver((entryList) => {
var list = entryList.getEntries();
var entry = list[list.length-1];
if(entry){
console.log('LongTask',entry.startTime)
}
}).observe({type: 'longtask', buffered: true});
一般我们取最后一个就是长任务的总耗时。这个值越低,性能越高。
二. 性能指标计算测试
贴出案例代码:
function test() {
const entry = performance.getEntriesByType('navigation')[0]
const {
domComplete, secureConnectionStart, domInteractive, domContentLoadedEventStart, domainLookupEnd,
domainLookupStart, connectEnd, connectStart, responseStart, requestStart, responseEnd, loadEventStart, domContentLoadedEventEnd, fetchStart, redirectEnd, redirectStart
} = entry
// redirectTime
const redirectTime = redirectEnd - redirectStart
// appcacheTime
const appcacheTime = domainLookupStart - fetchStart
// DNS解析时间
const dnsTime = domainLookupEnd - domainLookupStart
// TCP建立时间
const tcpTime = connectEnd - connectStart
// ssl 时间
const sslTime = connectEnd - secureConnectionStart
// requestTime 读取页面第一个字节的时间(请求时间)
const requestTime = responseStart - requestStart
// 返回响应时间
const responseTime = responseEnd - responseStart
// domContentLoadedEventEnd - domContentLoadedEventStart
const domLoaded = domContentLoadedEventEnd - domContentLoadedEventStart
// loadEventEnd - loadEventStart
const loadTime = loadEventStart - domContentLoadedEventEnd
const dom1 = domInteractive - responseEnd
// 解析dom树耗时
const dom2 = domComplete - domInteractive
console.log('********各个阶段的消耗耗时********')
console.log('redirectTime', redirectTime)
console.log('appcacheTime', appcacheTime)
console.log('dnsTime', dnsTime)
console.log('tcpTime', tcpTime, '其中包括ssl时间', sslTime)
console.log('TTFB(请求响应耗时)', requestTime)
console.log('Trans(内容传输耗时)', responseTime)
console.log('解析`DOM`阶段', dom1)
console.log('加载`DOM`阶段', dom2, '其中包括(domContentLoadedEventEnd - domContentLoadedEventStart)', domLoaded)
console.log('load事件耗时', loadTime)
console.log('********校验时间差********')
console.log('***********校验 responseStart - fetchStart差值**************');
console.log('responseStart - fetchStart', responseStart - fetchStart)
console.log('appCache + dns+ tcp + requestTime 总和:', appcacheTime + dnsTime + tcpTime + requestTime)
console.log('***********校验 domInteractive - fetchStart差值**************');
const tmp = appcacheTime + dnsTime + tcpTime + requestTime + responseTime
const diff = domInteractive - fetchStart
console.log('domInteractive - fetchStart', diff)
console.log( 'appCache + dns+ tcp + request + response, 总和:', tmp, ', 空白时间', diff - tmp)
console.log('空白时间(就是文档解析和构建DOM树的过程),即DOM1(domInteractive - responseEnd)', dom1)
// 算一下domCompelete - fetchStart
console.log('domCompelete - fetchStart', domComplete - fetchStart)
console.log('********FCP********')
const fcp = performance.getEntriesByName('first-contentful-paint')[0].startTime
console.log("LCP(通过performance.getEntriesByName('first-contentful-paint')计算出来的)", fcp)
console.log('********LCP********')
new PerformanceObserver((entryList) => {
var maxSize = 0;
var renderTime = 0;
for (var entry of entryList.getEntries()) {
// 渲染的内容看最大值
if (entry.size > maxSize) {
maxSize = entry.size;
renderTime = entry.startTime;
}
}
console.log('LCP', renderTime)
}).observe({ type: 'largest-contentful-paint', buffered: true });
console.log('********LongTask********')
new PerformanceObserver((entryList) => {
var list = entryList.getEntries();
var entry = list[list.length - 1];
if (entry) {
console.log('LongTask', entry.startTime)
}
}).observe({ type: 'longtask', buffered: true });
}
复制这段代码到浏览器中,然后运行test()即可,结果如下:(FCP打印错了)
代码里主要打印了各个阶段的耗时时长。我们主要看下下半部分的校验部分。再把上面的图搬过来对照着看:
2.1 衡量网络请求响应时间的指标
从发起网络请求(fetchStart)到服务器开始响应(responseStart)的时间间隔。
- responseStart - fetchStart的差值为257毫秒。
- 而这个差值由:appCache + dns+ tcp + requestTime 4个阶段连接而成,4个阶段的时间总和为256.5毫秒,基本上接近。
2.2 衡量页面加载速度的指标
从发起网络请求(fetchStart)到DOM解析完成(domInteractive)的时间间隔
- domInteractive - fetchStart的差值为1328毫秒。
- 而这个差值由:appCache + dns+ tcp + request + response + 空白时间 部分组成(空白时间就是上图的红色框部分)。
- 我们可以看到,前5个区域的时间总和大概是:480毫秒。空白时间则848毫秒。
- 我们又计算了domInteractive - responseEnd的差值,实际上就是DOM1,也就是加载DOM的时间,时间差为848毫秒,相吻合。
综上所述:
- 页面加载速度的指标可以由:DNS+TCP+Request+Response+DOM加载完毕耗时 的总和来决定。
另外我们还能看出来,LCP的计算可以几乎为:domInteractive - fetchStart, 当然你用PerformanceObserver进行监听也是可以的。
这里代表页面加载速度,此时DOM仅仅是解析完成,如果想看DOM也加载完成的耗时,看指标domComplete - fetchStart。
2.3 TTI(Time to Interactive)衡量页面可交互性的指标
TTI:表示从页面开始加载到用户可以与页面进行交互的时间。它的计算方式如下:
- FCP 时间为起始时间
- 查找到指示有5s的静默窗口时间(没有长任务并且不超过两个正在执行的GET请求)。
- 向后搜索静默窗口前的最后一个长任务,如果没有找到长任务,则在FCP上停止。
- TTI 是在安静窗口之前最后一个长任务的结束时间(如果没有找到长任务,则与FCP相同)
建议大家使用谷歌官方提供的:tti-polyfill
import ttiPolyfill from './path/to/tti-polyfill.js';
ttiPolyfill.getFirstConsistentlyInteractive(opts).then((tti) => {
// Use `tti` value in some way.
});
当然,也可以使用一种较为粗略的方式来计算:
- 首先我们理解一下TTI,是从页面开始加载到用户可以与页面进行交互的时间。
- 页面开始加载,我们是不是可以看做fetchStart的时间。
- 页面进行交互的时间,那这个时候dom肯定是加载完毕了。我们按照非常极限的思路去想,这个是不是可以看做dom加载完毕的时间点,即domComplete。
- 那么TTI ≈ domComplete - fetchStart
例如我用Lighthouse计算出来的TTI:
使用:domComplete - fetchStart 计算出来的值:
function getTTI(){
const entry = performance.getEntriesByType('navigation')[0]
const {
domComplete,
fetchStart
} = entry
console.log('TTI', domComplete - fetchStart)
}
getTTI()
结果如下:
2.4 TBT(Total Blocking Time)
TBT就是衡量从FCP时间点到TTI这个时间点的时间区间内,所有超过50毫秒的长任务的总耗时。(这个看下来难以通过编码的方式来实现计算,也无法预估)
2.5 总结
- 我们在为页面做性能监控的时候,LCP和FCP是我们的几个重要关注对象。
- LCP可以通过PerformanceObserver进行检测。
- FCP可以通过performance.getEntriesByName('first-contentful-paint')[0].startTime获取。
- 页面性能的发部分数据都可以从performance.getEntriesByType('navigation')[0]这里面获取到。
- 如果你想衡量网络请求响应时间的指标:responseStart - fetchStart,代表从发起网络请求(fetchStart)到服务器开始响应(responseStart)的时间间隔。
- 如果你想衡量页面加载速度的指标:domInteractive - fetchStart,代表从发起网络请求(fetchStart)到DOM解析完成(domInteractive)的时间间隔。
- domComplete - fetchStart 这个差值基本上囊括了最核心的部分。包括了从开始获取页面资源到 DOM 解析完成的整个过程,其中包括了网络请求、资源加载、解析 HTML、构建 DOM 树等操作。