web
的性能一定程度上影响了用户留存率,Google DoubleClick
研究表明:如果一个移动端页面加载时长超过 3
秒,用户就会放弃而离开。BBC
发现网页加载时长每增加 1
秒,用户就会流失 10%
。
一. 监控指标
Chrome
团队提出了一种 RAIL
模型来衡量应用性能,即: Response
(响应)、Animation
(动画)、Idle
(浏览器空置状态)和 Load
(加载)。如果在每个模块,都可以达到性能优化的目标值,那么最终用户感受到的将会是极致的体验。
-
Response
(响应) -100ms
如果用户点击了一个按钮,你需要保证在用户察觉出延迟之前就得到反馈。我们需要:
(1) 在首次收到输入时,在100毫秒内得到回应。
(2) 如果最终结果还需要花更长的时间得到,那也要给用户一个“加载中”的标识,或是颜色的变更,告诉用户“本产品已经接收到了指令,还在处理中”,不至于让用户自我怀疑。 -
Animation
(动画) -16ms
动画包含了以下概念:视觉动画、滚动、拖拽等。
合理地动画,每一帧动画要在16
毫秒内完成,才能达到60FPS
(1000ms/60 ~= 16.6 ms
) -
Idle
(浏览器空置状态) -50ms
浏览器空闲的时候再处理耗时任务。合理地应用浏览器空闲时间,最好把时间以50
毫秒为单位分组。因为应用应该在100
毫秒内给出响应,不应该出现一个模板渲染2
秒之久。
备注:Optimistic UI
-
Load
(加载) -1s
我们需要把最需要传达的内容在1
秒内渲染出来。我们要优先考虑关键渲染路径,将所有不需要在加载时处理的任务延迟到浏览器空闲时再处理。
二. Performance
浏览器提供的performance api
是性能监控数据的主要来源。performance
提供高精度的时间戳,精度可达纳秒级别,且不会随操作系统时间设置的影响。目前主流浏览器都支持。
-
performance.timing
对象
performance
对象的timing
属性指向一个对象,它包含了各种与浏览器性能有关的时间数据,提供浏览器处理网页各个阶段的耗时。
connectEnd: 1588117852573
connectStart: 1588117852492
domComplete: 1588117854411
domContentLoadedEventEnd: 1588117853956
domContentLoadedEventStart: 1588117853955
domInteractive: 1588117853955
domLoading: 1588117852694
domainLookupEnd: 1588117852492
domainLookupStart: 1588117852150
fetchStart: 1588117852137
loadEventEnd: 1588117854441
loadEventStart: 1588117854411
navigationStart: 1588117852120
redirectEnd: 0
redirectStart: 0
requestStart: 1588117852573
responseEnd: 1588117852670
responseStart: 1588117852667
secureConnectionStart: 1588117852505
unloadEventEnd: 1588117852680
unloadEventStart: 1588117852678
navigationStart
:当前浏览器窗口的前一个网页关闭,发生unload
事件时的Unix
毫秒时间戳。如果没有前一个网页,则等于fetchStart
属性。
unloadEventStart
:如果前一个网页与当前网页属于同一个域名,则返回前一个网页的unload
事件发生时的Unix
毫秒时间戳。如果没有前一个网页,或者之前的网页跳转不是在同一个域名内,则返回值为0
。
unloadEventEnd
:如果前一个网页与当前网页属于同一个域名,则返回前一个网页unload
事件的回调函数结束时的Unix
毫秒时间戳。如果没有前一个网页,或者之前的网页跳转不是在同一个域名内,则返回值为0
。
redirectStart
:返回第一个HTTP
跳转开始时的Unix
毫秒时间戳。如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0
。
redirectEnd
:返回最后一个HTTP
跳转结束时(即跳转回应的最后一个字节接受完成时)的Unix
毫秒时间戳。如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0
。
fetchStart
:返回浏览器准备使用HTTP请求读取文档时的Unix
毫秒时间戳。该事件在网页查询本地缓存之前发生。
domainLookupStart
:返回域名查询开始时的Unix毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart
属性的值。
domainLookupEnd
:返回域名查询结束时的Unix
毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart
属性的值。
connectStart
:返回HTTP
请求开始向服务器发送时的Unix
毫秒时间戳。如果使用持久连接(persistent connection
),则返回值等同于fetchStart
属性的值。
connectEnd
:返回浏览器与服务器之间的连接建立时的Unix
毫秒时间戳。如果建立的是持久连接,则返回值等同于fetchStart
属性的值。连接建立指的是所有握手和认证过程全部结束。
secureConnectionStart
:返回浏览器与服务器开始安全链接的握手时的Unix
毫秒时间戳。如果当前网页不要求安全连接,则返回0。
requestStart
:返回浏览器向服务器发出HTTP
请求时(或开始读取本地缓存时)的Unix
毫秒时间戳。
responseStart
:返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的Unix
毫秒时间戳。
responseEnd
:返回浏览器从服务器收到(或从本地缓存读取)最后一个字节时(如果在此之前HTTP
连接已经关闭,则返回关闭时)的Unix
毫秒时间戳。
domLoading
:返回当前网页DOM
结构开始解析时(即Document.readyState
属性变为 “loading”
、相应的readystatechange
事件触发时)的Unix
毫秒时间戳。
domInteractive
:返回当前网页DOM
结构结束解析、开始加载内嵌资源时(即Document.readyState
属性变为“interactive”
、相应的readystatechange事件触发时)的Unix
毫秒时间戳。
domContentLoadedEventStart
:返回当前网页DOMContentLoaded
事件发生时(即DOM
结构解析完毕、所有脚本开始运行时)的Unix
毫秒时间戳。
domContentLoadedEventEnd
:返回当前网页所有需要执行的脚本执行完成时的Unix
毫秒时间戳。
domComplete
:返回当前网页DOM
结构生成时(即Document.readyState
属性变为“complete”
,以及相应的readystatechange
事件发生时)的Unix
毫秒时间戳。
loadEventStart
:返回当前网页load
事件的回调函数开始时的Unix
毫秒时间戳。如果该事件还没有发生,返回0
。
loadEventEnd
:返回当前网页load
事件的回调函数运行结束时的Unix
毫秒时间戳。如果该事件还没有发生,返回0
。
根据上面这些属性,可以计算出网页加载各个阶段的耗时。比如,网页加载整个过程的耗时的计算方法如下:
var t = performance.timing;
var pageLoadTime = t.loadEventEnd - t.navigationStart;
重定向次数:performance.navigation.redirectCount
重定向耗时: redirectEnd - redirectStart
DNS
解析耗时: domainLookupEnd - domainLookupStart
TCP
连接耗时: connectEnd - connectStart
SSL
安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB)
: responseStart - requestStart
数据传输耗时: responseEnd - responseStart
DOM
解析耗时: domInteractive - responseEnd
资源加载耗时:loadEventStart - domContentLoadedEventEnd
首包时间: responseStart - domainLookupStart
白屏时间: responseEnd - fetchStart
首次可交互时间: domInteractive - fetchStart
DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart
http 头部大小: transferSize - encodedBodySize
-
performance.now()
返回当前网页自从performance.timing.navigationStart
到当前时间之间的毫秒数。
performance.now()
//61919.05499999848
Date.now() - (performance.timing.navigationStart + performance.now())
//-0.965087890625
performance.timing.navigationStart
加上performance.now()
,近似等于Date.now()
。但是,由于performance.now()
带有小数,因此精度更高。
通过两次调用performance.now()
方法,可以得到间隔的准确时间,用来衡量某种操作的耗时。
var start = performance.now();
for(let i = 0; i < 1000; i++) {}
var end = performance.now();
console.log('耗时:' + (end - start) + '毫秒。');
//耗时:0.029999995604157448毫秒。
-
performance.mark()
mark
方法用于为相应的视点做标记。
window.performance.mark('mark1');
window.performance.mark('mark2');
window.performance.getEntriesByType('mark');
clearMarks
方法用于清除标记,如果不加参数,就表示清除所有标记。
window.peformance.clearMarks('mark1');
window.performance.clearMarks();
-
performance.getEntries()
浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个HTTP
请求。performance.getEntries
方法以数组形式,返回这些请求的时间统计信息,有多少个请求,返回数组就会有多少个成员。
window.performance.getEntries()[0].duration
// 1567.8199999965727
-
performance.navigation
对象
除了时间信息,performance
还可以提供一些用户行为信息,主要都存放在performance.navigation
对象上面。
PerformanceNavigation {type: 1, redirectCount: 0}
(1) performance.navigation.type
该属性返回一个整数值,表示网页的加载来源,可能有以下4
种情况:
0
:网页通过点击链接、地址栏输入、表单提交、脚本操作等方式加载,相当于常数performance.navigation.TYPE_NAVIGATENEXT
。
1
:网页通过“重新加载”按钮或者location.reload()
方法加载,相当于常数performance.navigation.TYPE_RELOAD
。
2
:网页通过“前进”或“后退”按钮加载,相当于常数performance.navigation.TYPE_BACK_FORWARD
。
255
:任何其他来源的加载,相当于常数performance.navigation.TYPE_UNDEFINED
。
(2) performance.navigation.redirectCount
该属性表示当前网页经过了多少次重定向跳转。
-
performance.memory
描述内存多少,是在Chrome
中添加的一个非标准属性。
jsHeapSizeLimit: 2172649472
totalJSHeapSize: 47238861
usedJSHeapSize: 42139525
jsHeapSizeLimit
: 内存大小限制
totalJSHeapSize
: 可使用的内存
usedJSHeapSize
: JS
对象(包括V8
引擎内部对象)占用的内存,不能大于totalJSHeapSize
,如果大于,有可能出现了内存泄漏
-
Chrome Devtools Performance
https://segmentfault.com/a/1190000011516068
三. React Profile
-
开启高亮更新
- 查看性能数据
从概念上讲,React
分两个阶段工作:
(1)render
(渲染)阶段,确定需要对DOM
进行哪些更改。在此阶段,React
调用render
方法,然后将结果与之前的渲染进行比较。
(2)commit
(提交)阶段,是React
做出任何更新的阶段。(对于React DOM
来时,这是React
插入,更新和删除DOM
节点的时候。)React
也在这个阶段调用componentDidMount
和componentDidUpdate
等生命周期函数。
DevTools Profiler
(分析器) 根据commits
(提交) 对性能信息进行分组。commits
(提交) 显示在靠近Profiler
(分析器) 顶部的条形图中。
每个条形图的颜色和高度对应于commit
(提交) 渲染所需的时间 (较高的黄色竖条比较短的蓝色竖条耗时更长)。 - 过滤
commits
- 火焰图表(
Flame chart
)
火焰图表视图表示特定commits
(提交) 对应的应用的状态。 图表中的每个横条代表一个React
组件(例如App
,Nav
)。 横条的大小和颜色表示渲染组件及其子组件所需的时间。 (横条的宽度表示组件上次渲染时花费的时间,颜色表示当前commits
(提交) 部分所花费的时间。)
可以通过单击组件放大或缩小火焰图。 - 排序图表(
Ranked chart
)
排序图视图表示单个commit
。 图表中的每个横条代表一个React
组件。 对图表进行排序,以便渲染时间最长的组件位于顶部。
四. 监控工具
合成监控
-
Lighthouse
(1)chrome
运行
Lighthouse
是直接集成到chrome
开发者工具中的,位于Audits
面板下
(2) 本地运行
~ npm i -g lighthouse
~ lighthouse http://secure.finedevelop.com:65081/webroot/decision/url/mobile
-
PageSpeed
https://developers.google.com/speed/pagespeed/insights/
-
WebPageTest
https://www.webpagetest.org/
注释:关注
Start Render
、First Contentful Paint
、Speed Index
等性能指标。
真实用户监控
参考资料
如何进行 web 性能监控?
RAIL,以用户为核心的性能模型
Performance API
全新Chrome Devtools Performance使用指南
React性能测量和分析