浏览器的performance API与页面首屏加载分析
前言
现代浏览器提供了performance(性能)这个API来帮助我们分析页面的加载性能,从MDN上可以看到从IE9时代(约2011年)就开始支持了,所以目前来说兼容性还算可以,所以可以研究一下这个API具体有啥功能。
1. window.performance
1.1 整体结构
先看看performance主要结构:
其中页面整体的性能时间顺序都在timing属性中,主要结构:
想必大家一定想知道这里面各个字段都代表的啥,可以先看看这张图:
用console.table(window.performance.timing)整理下格式(按字段对应的含义发生的先后顺序排列)
1.2 字段含义
- navigationStart
为紧接着在相同的浏览环境下卸载前一个文档结束之时的 Unix毫秒时间戳
。如果没有上一个文档,则它的值相当于fetchStart
。- 可以直接理解为
页面加载开始的地方
。
- unloadEventStart
- 为 unload (en-US) 事件被触发之时的 Unix毫秒时间戳。
如果没有上一个文档,或者上一个文档或需要重定向的页面之一不同源,则该值返回 0
。 - 可以理解为
不同域,则为0,即加载新页面,就是0,可以被忽略
- 为 unload (en-US) 事件被触发之时的 Unix毫秒时间戳。
- unloadEventEnd
- 为unload (en-US) 事件处理程序结束之时的 Unix毫秒时间戳。如果没有上一个的文档,或者上一个文档或需要被跳转的页面的其中之一不同源,则该值返回 0。
不同域,则为0,即加载新页面,就是0,可以被忽略
- redirectStart
- 为第一个HTTP的重定向开始的时刻的 Unix毫秒时间戳。如果重定向没有发生,或者其中一个重定向非同源,则该值返回 0。
不同域,则为0,即加载新页面,就是0,可以被忽略
- redirectEnd
- 为最后一次的HTTP重定向被完成且HTTP响应的最后一个字节被接收之时的 Unix毫秒时间戳。如果没有发生重定向,或者其中一个重定向不同源,则该值返回 0。
不同域,则为0,即加载新页面,就是0,可以被忽略
- fetchStart
- 为浏览器已经准备好去使用HTTP请求抓取文档之时的 Unix毫秒时间戳。这一时刻在检查应用的缓存之前。
- 在没有重定向和页面卸载的情况下,和
navigationStart
的时间戳一样
- domainLookupStart
- 为域名开始解析之时的 Unix毫秒时间戳。如果一个持久连接被使用,或者该信息已经被本地资源或者缓存存储,则该值等同于
fetchStart
。 DNS已缓存,则为0,可忽略
- 为域名开始解析之时的 Unix毫秒时间戳。如果一个持久连接被使用,或者该信息已经被本地资源或者缓存存储,则该值等同于
- domainLookupEnd
- 为解析域名结束时的 Unix毫秒时间戳。如果一个持久连接被使用,或者该信息已经被本地资源或者缓存存储,则该值等同于
fetchStart
。 DNS已缓存,则为0,可忽略
- 为解析域名结束时的 Unix毫秒时间戳。如果一个持久连接被使用,或者该信息已经被本地资源或者缓存存储,则该值等同于
- connectStart
代表TCP开始建立连接时间节点
。如果浏览器没有进行TCP连接(比如使用持久化连接webscoket),则两者都等于domainLookupEnd;
- connectEnd
代表TCP连接完成的时间节点
。如果浏览器没有进行TCP连接(比如使用持久化连接webscoket),则两者都等于domainLookupEnd;
- secureConnectionStart
如果页面使用HTTPS,它的值是安全连接握手之前的时刻
。如果该属性不可用,则返回undefined。如果该属性可用,但没有使用HTTPS,则返回0
- requestStart
为浏览器发送从服务器或者缓存获取实际文档的请求之时的 Unix毫秒时间戳
。如果传输层在请求开始之后发生错误并且连接被重新打开,则该属性将会被设定为新的请求的相应的值 。
- responseStart
- 为浏览器从服务器、缓存或者本地资源接收到
响应的第一个字节
之时的 Unix毫秒时间戳。
- 为浏览器从服务器、缓存或者本地资源接收到
- responseEnd
- 为浏览器从服务器、缓存或者本地资源接收
响应的最后一个字节
或者连接被关闭之时的 Unix毫秒时间戳。
- 为浏览器从服务器、缓存或者本地资源接收
- domLoading
- 为解析器开始工作,即
Document.readyState
改变为 'loading
' 并且readystatechange
事件被触发之时的 Unix毫秒时间戳。
- 为解析器开始工作,即
- domInteractive
- 为在主文档的解析器结束工作,即
Document.readyState
改变为 'interactive
' 并且相当于readystatechange
事件被触发之时的 Unix毫秒时间戳。
- 为在主文档的解析器结束工作,即
- domContentLoadedEventStart
- 代表
DOMContentLoaded
事件触发的时间节点
- 代表
- domContentLoadedEventEnd
- 代表
DOMContentLoaded
事件结束的时间节点
- 代表
- domComplete
- 为主文档的解析器结束工作,
Document.readyState
变为 'complete
' - 相当于
readystatechange
事件被触发时的 Unix毫秒时间戳。
- 为主文档的解析器结束工作,
- loadEventStart
- 为
load
事件被现在的文档触发之时的 Unix时间戳。如果这个事件没有被触发,则他返回 0。
- 为
- loadEventEnd
- 为
load
事件处理程序被终止,加载事件已经完成之时的 Unix毫秒时间戳。如果这个事件没有被触发,或者没能完成,则该值将会返回 0。
- 为
1.3 字段分析
为了进一步确认各个节点对应的时间点,我们可以使用Chrome控制台的 NetWork
和 Performance
看看:
1.3.1 实践 Step 1
我们打开百度首页看看,得到的timing的值:
注意第二张图下显示的时间,得出
1616012122257(loadEventEnd
) - 1616012118346(navigationStart
) = 3911ms
等于 网页开始加载到Load事件完成所用的时间
1616012120068(domContentLoadedEventEnd
) - 1616012118346(navigationStart
) = 1722ms
等于 网页开始加载到DomContentLoaded完成所用的时间
1.3.2 实践 Step 2
那其他字段呢?我们再到 NetWork
找个资源看看
这里表示的是这个html资源从开始加载 => 加入队列(Queued at 0) => 排队等待(Queueing) => 暂停(Stalled) => SSL建立 => 发送请求(Request Sent) => Waiting(TTFB - Time to First Byte) => Content Download
首先解释一下各个阶段的含义:
Queued at 0
和Queueing
是资源加载队列排队的时间,这个跟浏览器当前处理事务的数量有关,不过一般比较小。Stalled
表示搁置时间,即这个时候即使开始处理他这个资源了,如果突然有其他事务要处理,那么就得耽搁他一下。SSL(安全套接层协议层)
,这个是在https的情况下才有,是在Web服务器和Web客户机之间建立经过身份验证和加密会话的Web协议。Request Sent
即开始发送请求的时候Waiting(TTFB)
首字节等待时间,即可以理解为Request Sent
到收到第一个字节
所需时间。Content Download
即内容下载时间,内容的大小和当时的网速会影响这个时间。
这里我举个比较好记的栗子:
假如你去银行办事
10
点到了银行, 那么Queued at 10:00
, 然后排队排了一小时
11
点到你的时候,柜员突然接到行长电话,要求高优处理一下其他业务,你就被搁置在那搁置了15分钟(Stalled
15分钟)11:15
终于到你了,然后柜员发现你级别有点高的样子,要求身份认证,这个时候让你又是输密码又是刷脸,最终花了15分钟走完这些流程(SSL
)11:30
终于可以开始办业务了,然后你开始问问题:我能取多少钱
?(Request Sent
)- 柜员又是新来的,所以她操作比较慢,拿你的卡,输入卡号,查询,计算,过了
10
分钟才告诉你卡上余额(Waiting TTFB
) - 由于你太有钱了,所以柜员取钱验钱再把钱一沓一沓给你的时间就花了
20
分钟(Content Download
)
通过上面的栗子可以容易想到,TTFB 是 反映服务端响应速度的重要指标,对服务器来说,TTFB 时间越短,就说明服务器响应越快。
影响TTFB时间长短的主要时间可能有:
- 浏览器端跟服务端之间的网络不好,如你在中国,服务器在非洲,那么你 "
发出的问题
" 要经过N多个网络节点才能到达非洲,这个时间肯定就长。(网络不好
) - 服务端收到浏览器端请求后,如果先读数据库,然后又同步处理大量数据,然后再把数据传给客户端,那么这个时间肯定也会长。(
耗时逻辑
)
一般服务端渲染的页面,其资源的TTFB会很长
1.3.3 实践 Step 3
上面的时间能够通过 performance API 来体现吗?实话告诉你还真可以。
我看了一下 performance API 上的方法,都简单说一说,先看整体结构:
再看一下方法的具体含义:
- performance.getEntries()
- 这个是返回浏览器加载一个网页所发出的静态资源(也包含webworker)的
性能记录列表
- 这个是返回浏览器加载一个网页所发出的静态资源(也包含webworker)的
每一项的结构至少有:
{
name: '资源的名字',
entryType: '资源的类型,如resource 表示是静态资源,paint 表示是渲染事件',
startTime: '资源请求的开始时间(注意不是时间戳,而是相对于页面加载起始的偏移时间)',
duration: '资源请求的耗时'(资源加载开始 => 资源加载结束)
}
举2个栗子:
- 静态资源类型:
- 渲染类型:
可以看到除了上面说的字段还有很多其他字段,其实该对象还扩展了几个其他对象的属性,包括 PerformanceMark, PerformanceMeasure , PerformanceFrameTiming, PerformanceNavigationTiming 以及 PerformanceResourceTiming.
我们再举个两个栗子,看看他们对应的字段到底对应哪个时间点:
- 这一种是重定向资源(设为A),类似于页面加载,所以有load事件
- 这一种是静态资源类型(设为B)
重定向资源类型(A)对应的注释:
{
// TCP链接开始
connectEnd: 29.009999999288993,
// TCP链接完成
connectStart: 10.269999999763968,
// 解编码后大小
decodedBodySize: 320036,
// dom完成(包括解析)
domComplete: 1022.3799999994299,
// 对应页面的DomContentLoaded事件,DomContentLoaded结束时间
domContentLoadedEventEnd: 705.3900000000795,
// DomContentLoaded开始时间
domContentLoadedEventStart: 692.4199999994016,
// dom可交互时间
domInteractive: 519.4899999996778,
// DNS解析完成
domainLookupEnd: 10.269999999763968,
// DNS解析开始
domainLookupStart: 5.699999999706051,
// 排期
duration: 1030.9849999994185,
// 编码后大小
encodedBodySize: 78155,
// 资源类型 导航重定向
entryType: "navigation",
// 开始请求的事件
fetchStart: 0.534999999217689,
// 具体的触发者的类型,如 navigation link script 等
initiatorType: "navigation",
// 对应页面的load事件完成时间
loadEventEnd: 1030.9849999994185,
// 对应页面的load事件开始时间
loadEventStart: 1022.4199999993289,
// 请求资源名字
name: "https://www.baidu.com/",
nextHopProtocol: "http/1.1",
// 重定向次数
redirectCount: 0,
// 重定向完成
redirectEnd: 0,
// 重定向开始
redirectStart: 0,
// 请求开始
requestStart: 29.144999999516585,
// 数据响应完成,即数据下载完成
responseEnd: 253.8349999995262,
// 数据响应开始,即数据刚开始下载
responseStart: 221.83999999924708,
// SSL时间
secureConnectionStart: 16.709999999875436,
serverTiming: [],
// 资源开始触发时间
startTime: 0,
// 传输大小
transferSize: 78846,
// 加载类型,目前看是navigation下才有
type: "reload",
// 上一个页面卸载完成时间
unloadEventEnd: 251.25499999921885,
// 上一个页面卸载开始时间
unloadEventStart: 250.11999999969703,
workerStart: 0,
}
B的注释就不说了,主要差别就是没有load DOMContentLoaded这些事件。
1.3.4 实践 Step 4
再看看A和B的NetWork
A:
B:
你会发现
A:
responseEnd(253.8349999995262) - startTime(0) = 右下角的总时间(253.8,约等于253.29,有点误差)
loadEventEnd(1030.9849999994185)- startTime(0) = duration(1030.9849999994185)
responseEnd(253.8349999995262)- responseStart(221.83999999924708) = Wating(TTFB)s时间(实际值:31.995000000279106,TTFB显示值:31.93)
domainLookupEnd (10.269999999763968) - domainLookupStart(5.699999999706051) = DNS Lookup(实际值:4.570000000057917,显示值:4.57)
connectEnd(29.009999999288993)- connectStart(10.269999999763968) = Initial connection (实际值:18.739999999525025,显示值:18.75)
connectEnd(29.009999999288993) - secureConnectionStart (16.709999999875436)= SSL(实际值:12.299999999413558,显示值:12.30)
fetchStart的值为0.534999999217689,这个时间目前还无法确定是在Queueing之前还是在Stalled之后
B:
responseEnd(325.6149999997433) - startTime(275.24999999968713) = 右下角的总时间(50.36,约等于50.38,有点误差)= duration(50.36500000005617)
B的数据有些为0,估计是有缓存或者其他原因,所以不一一计算。
1.3.5 实践 Step 5
我们再看看Performance面板里A对应图
我仔细计算了一下
0对应的是TTFB以前的时间(0 - 29ms),耗时29 (18.75 + 4.57 + 2.88 + 2.26 = 28.46)
1对应的是TTFB
(29 - 221.93),耗时192.93
2对应的是Content Download
(221.93 - 253.83),耗时31.9
经过上面的例子,我们可以得出(以下代指他们的耗时
):
DNS Lookup = domainLookupEnd - domainLookupStart
Initial connection = connectEnd - connectStart
SSL = connectEnd - secureConnectionStart
TTFB = responseEnd - responseStart
发生的顺序 DNS Lookup => Initial connection(SSL包含在connection内) => TTFB
1.3.5 实践 Step 5
上面是对单个资源对应performance.getEntries()
的解释,我们再来看看performance.timing
对应整个网页加载流程的解释。
为了验证可靠性,我分别打开了三个不同的网页,而且把网络缓存
都关了
performance.timing
值:
navigationStart 1616233754952
unloadEventStart 1616233755153
unloadEventEnd 1616233755156
redirectStart 0
redirectEnd 0
fetchStart 1616233754956
domainLookupStart 1616233754956
domainLookupEnd 1616233754956
connectStart 1616233754956
connectEnd 1616233754956
secureConnectionStart 0
requestStart 1616233754974
responseStart 1616233755144
responseEnd 1616233755147
domLoading 1616233755159
domInteractive 1616233755723
domContentLoadedEventStart 1616233755723
domContentLoadedEventEnd 1616233755729
domComplete 1616233756837
loadEventStart 1616233756837
loadEventEnd 1616233756841
Performance面板
我们要关注的是图中FP
/FCP
/LCP
/L
发生的时刻和performance.timing内时刻的关系
这次
FP FCP LCP
时间一致:358.4ms
DCL
: 787.3ms
L
:1898.6ms
我们把 navigationStart
(1616233754952) 的时间点作为初始时间:
FP FCP LCP
:1616233754952 + 358.4 = 1616233755310.4 ,(在 domLoading
1616233755159 之后)
DCL
: 1616233754952 + 787.3 = 1616233755739.3 ,在 domContentLoadedEventEnd
1616233755729 附近,可以说约等于,但是差不了多少
L
: 1616233754952 + 1898.6 = 1616233756850.6 ,在 loadEventEnd
1616233756841 附近,也是约等于
navigationStart 1616234946001
unloadEventStart 1616234946335
unloadEventEnd 1616234946335
redirectStart 0
redirectEnd 0
fetchStart 1616234946003
domainLookupStart 1616234946003
domainLookupEnd 1616234946003
connectStart 1616234946003
connectEnd 1616234946003
secureConnectionStart 0
requestStart 1616234946011
responseStart 1616234946254
responseEnd 1616234946273
domLoading 1616234946344
domInteractive 1616234947467
domContentLoadedEventStart 1616234947467
domContentLoadedEventEnd 1616234947469
domComplete 1616234949225
loadEventStart 1616234949225
loadEventEnd 1616234949227
FP FCP LCP
:504.7 ms
DCL
:1475.1 ms
L
: 3231.3 ms
FP FCP LCP
:1616234946001 + 504.7 = 1616234946505.7 ,(在 domLoading
1616234946344 之后)
DCL
: 1616234946001 + 1475.1 = 1616234947476.1 ,在 domContentLoadedEventEnd
1616234947469 附近,可以说也是约等于,但是差不了多少
L
: 1616234946001 + 3231.3 = 1616234949232.3 ,在 loadEventEnd
1616234949227 附近,也是约等于
- 网页三:
navigationStart 1616236044085
unloadEventStart 1616236044641
unloadEventEnd 1616236044641
redirectStart 0
redirectEnd 0
fetchStart 1616236044088
domainLookupStart 1616236044088
domainLookupEnd 1616236044088
connectStart 1616236044088
connectEnd 1616236044088
secureConnectionStart 0
requestStart 1616236044093
responseStart 1616236044632
responseEnd 1616236044633
domLoading 1616236044642
domInteractive 1616236045952
domContentLoadedEventStart 1616236045952
domContentLoadedEventEnd 1616236045953
domComplete 1616236046263
loadEventStart 1616236046263
loadEventEnd 1616236046272
FP FCP
: 2195.5 ms
LCP
: 2191.3 ms
DCL
: 1872.6 ms
L
: 2191.3 ms
FP FCP
:1616236044085 + 2195.5 = 1616236046280.5 ,(居然在L附近)
LCP
: 1616236044085 + 2191.3 = 1616236046276.3,也在L附近
DCL
: 1616236044085 + 1872.6 = 1616236045957.6 ,在 domContentLoadedEventEnd
1616236045953 附近,可以说也是约等于,但是差不了多少
L
: 1616236044085 + 2191.3 = 1616236046276.3 ,在 loadEventEnd
1616236046272 附近,也是约等于
所以综合上面几个例子,网页加载的事件中他们的耗时公式:
L = loadEventEnd - navigationStart
DCL = domContentLoadedEventEnd - navigationStart
以上2个公式是确定的
总结
- 针对单个资源而言,其加载时间:
发生的时间顺序:
DNS Lookup => Initial connection(SSL包含在connection内) => TTFB
耗时:
DNS Lookup = domainLookupEnd - domainLookupStart
Initial connection = connectEnd - connectStart
SSL = connectEnd - secureConnectionStart
TTFB = responseEnd - responseStart
- 针对整个网页加载而言
耗时:
L = loadEventEnd - navigationStart
DCL = domContentLoadedEventEnd - navigationStart
FP
目前看发生在 domLoading
之后,但是个人认为(参考这个 Chrome的First Paint触发的时机探究)也可能在其之前
FCP/LCP
肯定发生在 domLoading
之后,但与 DCL
和 L
的先后没有太大的关系,有可能再其前也可能在其后(具体关系后面再探究)
没写完,有空再写