最近打算给WEB应用收集一些打开的相关数据,所以需要对performance
的api进行熟悉。收集完数据之后,后续有什么故障或者优化的手段,就可以使用数据说话,而不是说,“加入了XX的优化,快了很多”,“服务器负载过高,影响了XX的部分的解析时间”。
接下来主要分析performance.timing
对象所在属性,对于performance
对象的其他属性不做详细处理。这篇文章的也没什么特别的地方,只是在面向谷歌研究的时候,发现很多关于performance.timing
的描述都很模棱两可,而且比较简单,对于实际情况应用与影响因素不明确,所以做了部分测试。
performance.timing 基础介绍
performance.timing
对象的数据是什么?该对象下包含页面打开的各种的时间戳,例如:DNS 耗费时间,TCP 连接时间等;这些时间不通过performance
的api,暂时无法获取这些相对底层的时间。这个包含以下字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
navigationStart unloadEventStart unloadEventEnd redirectStart redirectEnd fetchStart domainLookupStart domainLookupEnd connectStart connectEnd secureConnectionStart requestStart responseStart responseEnd domLoading domInteractive domContentLoadedEventStart domContentLoadedEventEnd domComplete loadEventStart loadEventEnd |
这些字段顺序触发是怎样的?参考alloyteam抄回来的这张图:
所以调整以下,整体顺序是这样子的:
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 32 33 34 35 36 37 38 39 40 |
{
// unload 事件触发的开始与结束时间
// unload 事件指的是上一个页面(跳转到当前页面)或者当前页面(刷新页面)监听的事件: window.addEventListener('unload', () => {})
// 这个通常是这个事件的回调函数所耗费的事件,感觉不太重要?
unloadEventStart,
unloadEventEnd,
// 比较好理解,重定向所花的时间,若中途包含多次重定向才能确定最终的地址,则耗费的时间随之上升
redirectStart,
redirectEnd,
// alloyteam描述:在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
// 个人理解,当有 unload 事件的时候,与unloadEventStart时间一致,否则与 fetchStart 时间一致
navigationStart,
// 已找到目标地址,开始获取文档
fetchStart,
// DNS 解析时间
domainLookupStart,
domainLookupEnd,
// DNS 解析到对应的ip,开始建立TCP
connectStart,
// 如果是加密https,则TCP加密有这一步,如果是http协议,则这个值为0
secureConnectionStart,
connectEnd,
// TCP 已建立,准备发出http请求
requestStart
// 接收文档数据
responseStart
responseEnd
// dom 解析
domLoading
// dom 解析完毕
domInteractive
// 触发 DOMConctentLoaded 事件
domContentLoadedEventStart
domContentLoadedEventEnd
// dom 已准备好,对应的资源也已经ok
domComplete
// 触发 load 事件
loadEventStart
loadEventEnd
} |
影响不同事件的因素
unload 相关
对应unloadEventStart
,unloadEventEnd
, 这个是unload
时间的执行整个时间;有时候从别的页面跳转过来,控制权不在自己所在应用,通常来说意义不是很大。
影响因素:上一次unload
事件处理任务
redirect 相关
对应redirectStart
,redirectEnd
, 这个比较明显,在达到最终域名之前,重定向所花的时间;通常重定向收集部分数据或者链接更改。
影响因素:redirect 次数
DNS 相关
对应domainLookupStart
,domainLookupEnd
, 这个通常是指该链接文档对应的'index.html'
所在链接的DNS
解析时间,不是页面所有链接的DNS所花时间
影响因素:域名供应商?[狗头]
TCP 相关
对应connectStart
,connectEnd
,secureConnectionStart
事件,建立TCP
链接所耗费时间
影响因素:服务器对请求响应速度
文档接收发出与收到时间
对应requestStart
,responseStart
,responseEnd
事件;从requestStart
到responseStart
这段时间,是服务器处理生成文档的过程;而responseStart
到responseEnd
的时间,是客户端下载整个文档所花的时间;
影响因素:1. 确认文档:服务器处理文档的时间;2. 下载过程:客户端与服务端的网络速度;
以上几个因素对于基础网络研究不深入,所以只能点一些皮毛,比较浅显的影响因素
DOM 树解析过程
对应domLoading
, domInteractive
;从dom开始解析到dom解析完毕;这里的domInteractive
响应,已经包含了对应的静态资源;这里的静态资源指的是index.html
所在的包含的同步css
,js
等。不包含图片。同步js
文件发出新的请求加入的js文件,这些文件加载不影响这个过程的时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<head>
<!-- 同步 css 加载时间影响 -->
<link rel="stylesheet" href="index.css">
</head>
<body>
<!-- 图片加载时间不影响 -->
<img src="/big.jpg" />
<!-- 同步js影响 domInteractive 时间 -->
<script src="index.js"></script>
<!-- 异步 js 文件加载时间不影响 -->
<script src="defer.js" defer></script>
<script src="async.js" async></script>
</body> |
1 2 3 4 5 |
// index.js
var script = document.createElement('script')
// 后续加入的js文件加载时间不影响这个过程
script.src = '/append-by-index.js'
document.head.appendChild(script) |
影响因素:
- dom 树的节点数量
- 文档包含的同步
css
,js
文件的大小与加载时间;
DOM 解析完毕到触发 DOMContentLoaded 事件
对应domInteractive
, domContentLoadedEventStart
;这个时候dom已经解析完毕,按道理来说应该马上进入domContentLoadedEventStart
事件,还有其他影响吗?也是我们上面提到的,标识为defer
的script
标签如果在domInteractive
触发后还没加载好,那么这个时候就会影响到。
这是因为defer
的特性,defer
的js文件会在DOMContentLoaded
事件触发前执行,执行完毕再触发DOMContentLoaded
事件。
与defer
类似的async
异步script
,async
的js文件没有这个限制,而是在下载完毕后,马上执行,与DOMContentLoaded
事件没有关系。
影响因素:
- 设置
defer
的js文件加载速度
DOMContentLoaded 事件执行时间
对应domContentLoadedEventStart
与domContentLoadedEventEnd
,这个比较简单,就是DOMContentLoaded
回调事件函数执行的时长。
影响因素:
DOMContentLoaded
事件回调函数执行时间
DOM 最终完成
对应domComplete
事件;这里有点疑惑,dom在上面不是已经完成了,这里怎么还有一个domComplete
事件。因为这个domComplete
事件是应用中所有的资源,包括图片等都已经加载完了,才会触发domComplete
事件
影响因素:
- 页面所有资源的加载速度
loaded执行时间
这个与DOMContentLoaded
类似,是load
事件的执行时间:
1
|
window.addEventListener('load', () => {}) |
影响因素:
load
回调函数的执行时长
收集指标
通过上面可以知道哪些因素影响哪一部分的效果,通常来说,收集的指标有以下几种:
1 2 |
// 后续的performance.timing都是使用这个变量代替
var timing = performance.timing |
- DNS 解析时间:
timing.domainLookupEnd - timing.domainLookupStart
- TCP 建立时间:
timing.connectEnd - timing.connectStart
- redirect 时间:
timing.redirectEnd - timing.redirectStart
- ttfb:
timing.responseStart - timing.requestStart
有些统计会把DNS解析或者TCP连接都归类到ttfb的时间,但是从chrome开发者工具看到,ttfb只包含从请求发起到收到的时间,所以这里也是选用了这种 ttfb wiki - 文档下载时间:
timing.responseEnd - timing.responseStart
,反映该请求文档的下载时间,服务器是否处理过长 - DOM 解析与基础静态文件下载时间:
timing.domInteractive - timing.domLoading
;若时间过长,则考虑 DOM 节点的问题与基础静态文件体积大小,是否对入口文件进行拆分,使用懒加载功能;是否对静态文件加入到CDN,是否改成http2等 - 所有资源加载速度:
timing.domComplete - timing.domContentLoadedEventEnd
;图片资源是否过大,非必要进页面展现的图片使用懒加载占位等,图片资源是否也加入cdn,http2等.....
思考
在domLoading
事件之前的指标都是只与html文档有关系。对于SPA应用来说,实际上DOM元素没多少个,大部分元素都是后面进行渲染,所以是否需要对后续渲染的过程再做一层处理,收集app启动的时间?收集的启动时间没有对应在timing
对象,需要通过performance.measure
来处理,下面以vue与react为例子进行一个简单例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// vue main.js
import xx from 'xx'
// ...
performance.mark('appBootStart')
new Vue({
el: '#app',
mounted() {
// 使用 setTimeout 是使用宏任务保证可交互状态
setTimeout(() => {
performance.mark('appBootEnd')
performance.measure('bootDuration', 'appBootStart', 'appBootEnd')
const { duration } = performance.getEntriesByName('bootDuration')[0]
// upload duration time
})
}
}) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// react main.js
import xx from 'xx'
// ...
performance.mark('appBootStart')
const app = () => {
// componentDidMounted hook
useEffect(() => {
setTimeout(() => {
performance.mark('appBootEnd')
performance.measure('bootDuration', 'appBootStart', 'appBootEnd')
const { duration } = performance.getEntriesByName('bootDuration')[0]
// upload duration time
})
}, [])
} |
如果根节点的mount
事件并不能充分展现应用的实际情况,那么需要更改到其他组件下进行处理,才能记录真实应用的情况;反馈有内容的第一次可交互时间。
参考文章: