项目上线后,通常都是都会做埋点,做数据监控、异常监控以及性能监控,本文主要聊聊前端性能监控。重点讲performance怎么做性能监控。
前端性能监控通常检测某些方面的加载时间,通过得到的加载时间的长短来判断项目某方面的性能怎样,具体是哪些方面呢???我们先来看下页面加载是一个怎样的过程:
页面加载
页面加载的方式有两种,一种是加载完资源文件后通过javascript动态获取接口数据,然后数据返回渲染内容,这就是前后端分离的项目页面加载方式。另一种就是服务器渲染,同构直出前端页面,这种方式相对前一种方式页面加载的速度要快很多,性能方面相对也就要好些。但是目前大部分的项目都是前后端分离,本文重点描述这种方式。
- 输入网址,回车
- 缓存解析:如果浏览器本地缓存有资源从缓存取资源
- 域名解析:
DNS解析,将域名解析成IP,如果缓存中存在,直接丛缓存中取IP,不用做域名解析 - 发送请求:向服务器发送请求
TCP连接、三次握手:建立浏览器端和服务器端连接- 服务器接到请求:服务器响应请求
- 数据传输
- 浏览器端拿到数据,解析
html文件,构建DOM树,CSSOM树,js文件的加载可能会阻塞页面的渲染 - 初始的
html被完全加载和解析后会触发DOMContentLoaded事件 CSSOM树和DOM树构建完成后会开始生成Render树,绘制- 在生成
Render树的过程中,浏览器就开始调用GPU绘制,合成图层,将内容显示在屏幕上 - 没有文件传输,四次挥手,
TCP连接断开
上面就是页面加载的基本过程,没有展开讲,如果要展开讲的话,十篇文章也讲不完,在本文中只需要清楚大概的整个过程,后期安排系统的整理上述整个过程的内容,做文字输出。
页面加载整个过程中主要分为:白屏、重定向、DNS查询、TCP连接、HTTP请求、DOM解析、DOMready、onload等,这些也就是我们前端性能监控包括的主要方面。我们需要监控这几个方面的数据,做数据分析,进一步做前端性能优化。例如如何加快首屏加载时间、减少http请求时间等等。
下面讲讲本文主角,performance,不是Chrome开发者工具的Performance面板,当然Chrome开发者工具也能做性能监控。
performance
performance是前端性能监控的API,可以获取到当前页面中也与性能相关的信息。
我们来看看天猫商城首页通过window.performance这个API,获取到的一些信息:

从上面的信息可以看到
window.performance是一个对象,包含了四个属性:
memory、
navigation、
timeOrigin、
timing,以及一个事件处理程序
onresourcetimingbufferfull,我们再来看看这几个分别代表什么?
performance.memory
在Chrome中添加的一个非标准扩展,memory这个属性提供了一个可以获取到基本内存使用情况的对象MemoryInfo
performance.memory = {
jsHeapSizeLimit, // 内存大小限制,单位是字节B
totalJSHeapSize, // 可使用的内存大小,单位是字节B
usedJSHeapSize // JS对象占用的内存大小,单位是字节B
}
如果usedJSHeapSize的值大于totalJSHeapSize,会出现内存泄露的问题,所以不能大于totalJSHeapSize的值。
performance.navigation
返回PerformanceNavigation对象,提供了在指定的时间段发生的操作相关信息,包括页面是加载还是刷新、发生了多少重定向等。
performance.navigation = {
redirectCount: xxx,
type: xxx
}
PerformanceNavigation对象有两个属性,redirectCount和type。
performance.navigation.redirectCount
只读属性,表示达到这个页面之前经过多少次重定向跳转,但是这个接口有同源策略的限制,仅能检测到同源的重定向。
performance.navigation.type
只读属性,有四个返回值:0,1,2,225:
- 0:表示当前页面是通过点击链接,书签和表单提交,或者脚本操作,或者在
url中直接输入地址,相当于常数performance.navigation.TYPE_NAVIGATE - 1:表示点击刷新页面按钮或者通
过Location.reload()方法显示的页面,相当于常数performance.navigation.TYPE_RELOAD - 2:表示页面通过历史记录和前进后退访问的,相当于常数
performance.navigation.TYPE_BACK_FORWARD - 225:表示任何其他加载方式,相当于常数
performance.navigation.TYPE_RESERVED
performance.timeOrigin
返回性能测量开始的时间的高精度时间戳。

上面的时间就表示开始性能测试的时间。
performance.onresourcetimingbufferfull
一个回调的EventTarget,当触发resourcetimingbufferfull事件的时候会被调用。
performance.timing
返回PerformanceTiming对象,包含了各种与浏览器性能相关的数据,提供了浏览器处理页面的各个阶段的耗时,其整体结构可以参考下图:

PerformanceTiming对象中的属性都是只读属性,值都是精确到
Unix毫秒的时间戳:
navigationStart
返回当前浏览器窗口的前一个页面的关闭,发生unload事件时的时间戳。如果没有前一个页面,则等于fetchStart属性。
unloadEventStart
返回如果前一个页面与当前页面同域,则返回前一个页面unload事件发生时的时间戳。如果没有没有前一个页面,或者之前的页面跳转不是在同一个域名内,则返回值为0
unloadEventStart
和unloadEventStart相对应,返回前一个页面unload事件绑定的回调函数执行完毕的时间戳。如果没有没有前一个页面,或者之前的页面跳转不是在同一个域名内,则返回值为0
redirectStart
返回第一个http重定向发生时的时间戳。有跳转并且是同域名内的重定向,否则返回值为0
redirectEnd
返回最后一个http重定向完成时的时间戳。有跳转并且是同域名内的重定向,否则返回值为0
fetchStart
返回浏览器准备好使用http请求抓取文档的时间戳,这发生在检查本地缓存之前
domainLookupStart
返回DNS域名查询开始的时间戳,如果使用了本地缓存(也就是没有做DNS查询,直接从缓存中取到IP)或者使用了持久连接,则与fetchStart值相等
domainLookupEnd
返回DNS域名查询完成的时间戳,如果使用了本地缓存(也就是没有做DNS查询,直接从缓存中取到IP)或者使用了持久连接,则与fetchStart值相等
connectStart
返回http(TCP)开始建立连接的时间戳,如果是持久连接,则与fetchStart值相等。如果在传输层发生了错误并且重新建立连接,则这里显示的是新建立的连接开始的时间戳
connectEnd
返回http(TCP)完成建立连接的时间戳,完成了四次握手,如果是持久连接,则与fetchStart值相等。如果在传输层发生了错误并且重新建立连接,则这里显示的是新建立的连接完成的时间戳。连接建立指的是所有握手和认证过程全部结束
secureConnectionStart
返回https连接开始的时间戳,如果不是安全连接,否则返回值为0
requestStart
返回http请求读取真实文档开始的时间戳(完成建立连接),包括从本地读取缓存。如果连接错误重连时,这里显示的也是新建立连接的时间戳
responseStart
返回http开始接收响应的时间戳(获取到第一个字节),包括从本地读取缓存
responseEnd
返回http响应全部接收完成的时间戳(获取到最后一个字节),包括从本地读取缓存
domLoading
返回开始解析渲染DOM树的时间戳,此时Document.readyState变为loading,并将抛出readystatechange相关事件
domInteractive
返回完成解析DOM树的时间戳,Document.readyState变为interactive,并将抛出readystatechange相关事件。这里只是DOM树解析完成,这时候并没有开始加载网页内的资源
domContentLoadedEventStart
返回DOM解析完成后,网页内资源加载开始的时间戳。即所有需要被执行的脚本开始被解析了。在DOMContentLoaded事件抛出前发生
domContentLoadedEventEnd
返回DOM解析完成后,网页内资源加载完成的时间戳。例如JS脚本加载执行完成,不论执行顺序。DOMContentLoaded事件也已经完成
domComplete
返回DOM解析完成,且资源也准备就绪的时间戳。Document.readyState变为complete,并将抛出readystatechange相关事件
loadEventStart
返回load事件发送给文档,load回调函数开始执行的时间戳。如果没有绑定load事件,返回值为0
loadEventEnd
返回load事件的回调函数执行完毕的时间戳。如果没有绑定load事件,返回值为0
上面已经解释了相关属性的含义,通过上面的数据能做很多帮助我们做性能监控的事情:
- 页面加载完成时间:代表了用户等待页面可用的时间
let performance = window.performance;
let t = performance.timing;
let time = t.loadEventEnd - t.navigationStart;
- 解析DOM树结构的时间:判断
DOM树嵌套情况
let performance = window.performance;
let t = performance.timing;
let time = t.domComplete - t.responseEnd;
- 重定向的时间
let performance = window.performance;
let t = performance.timing;
let time = t.redirectEnd - t.redirectStart;
DNS查询时间:可做预加载,缓存,减少查询时间
let performance = window.performance;
let t = performance.timing;
let time = t.domainLookupEnd - t.domainLookupStart;
- 白屏时间:读取页面第一个字节的时间
let performance = window.performance;
let t = performance.timing;
let time = t.responseStart - t.navigationStart;
- 内容加载完成的时间
let performance = window.performance;
let t = performance.timing;
let time = t.responseEnd - t.requestStart;
- 执行
onload回调函数的时间
let performance = window.performance;
let t = performance.timing;
let time = t.loadEventEnd - t.loadEventStart;
DNS缓存时间
let performance = window.performance;
let t = performance.timing;
let time = t.domainLookupStart - t.fetchStart;
- 卸载页面的时间
let performance = window.performance;
let t = performance.timing;
let time = t.unloadEventEnd - t.unloadEventStart;
TCP建立连接完成握手的时间
let performance = window.performance;
let t = performance.timing;
let time = t.connectEnd - t.connectStart;
我们可以计算出页面加载的过程中各阶段的耗时,根据时长判断某阶段的性能怎么样,再做进一步的优化处理。这是性能优化很重要的一步,我们需要定位到具体的哪个阶段耗时过长,对症下药。
performance也提供了一些方法,我们来看看一些常用的方法:
自定义统计方法
performance.mark()
创建一个DOMHighResTimeStamp保存在资源缓存数据中,可通过performance.getEntries()等相关接口获取。简单的理解就是可以做标记,也就是“打点”
performance.mark(name);
performance.clearMarks()
用于清除标记,如果不加参数,就表示清除所有标记。
performance.clearMarks(name); // 清除指定标记
performance.clearMarks(); // 清除所有标记
performance.measure()
计算两个mark之间的时长,创建一个DOMHighResTimeStamp保存在资源缓存数据中,可通过performance.getEntries()等相关接口获取。
performance.measure(name, startMark, endMark);
performance.clearMeasures
移除缓存中所有entryType为measure的资源数据。
performance.clearMeasures(name); // 清除指定记录间隔数据
performance.clearMeasures(); // 清除所有记录间隔数据
上面的四个API,可以自定义统计一些数据,例如统计某函数的执行时间。
在Vue中也有用到,为了追踪组件的性能,在Vue2.X中全局配置API有这么个方法:
Vue.config.performance = false;
设置为true以在浏览器开发工具的性能/时间线面板中启用对组件初始化、编译、渲染和打补丁的性能追踪。只适用于开发模式和支持performance.mark API的浏览器上。我们入口文件中开启,开启后可以使用Vue Performance Devtool 这个chrome插件来查看各组件加载情况:
if (process.env.NODE_ENV !== 'production') {
Vue.config.performance = true;
}

在
Vue源码中,也是通过
performance.mark和
performance.measure来实现的,我们看下具体的源码实现:
// vue/src/core/util/perf.js
import { inBrowser } from './env'
export let mark
export let measure
if (process.env.NODE_ENV !== 'production') { // 环境判断 开发环境执行下面程序
const perf = inBrowser && window.performance // 浏览器环境
/* istanbul ignore if */
if (
perf &&
perf.mark &&
perf.measure &&
perf.clearMarks &&
perf.clearMeasures
) {
mark = tag => perf.mark(tag) // 给定tag打点,做标记
measure = (name, startTag, endTag) => {
perf.measure(name, startTag, endTag) // 计算两个mark之间的时长
perf.clearMarks(startTag) // 清除startTag标记
perf.clearMarks(endTag) // 清除endTag标记
// perf.clearMeasures(name) // 清除指定记录间隔数据
}
}
}
从上面的代码可以看出,尤大大通过mark和measure两个函数对performance.mark()和performance.measure()进行了封装。我们再来看看在源码中怎么应用的:
// vue/src/core/instance/init.js
import { mark, measure } from '../util/perf'
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag) // 开始标记
}
// .... 中间代码省略
// 一系列初始化函数
// initLifecycle(vm)
// ....
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag) // 结束标记
measure(`vue ${vm._name} init`, startTag, endTag) // 计算两个mark之间的时长
}
}
}
上面是Vue初始化实现的相关程序,上面的打点标记就是为了追踪组件初始化的性能情况,就是在初始化的代码的开头和结尾分别使用mark函数打上两个标记,然后通过measure函数对这两个标记点进行性能计算。在编译、渲染和打补丁也做了性能追踪,感兴趣的同学可以看看。
上面是Vue中对performance的应用,起初看vue源码的时候看到这块不是很明白,不影响整体的阅读,没有太关系,最近工作上有用到performance,想到Vue好像也用到了,就回头看看Vue中的应用,现在是整明白了。我们再来看看一个比较简单实例,更好的理解下:
performance.mark('markStart'); // 标记一个开始点
for(let i = 0; i < 10; i++) {
console.log(i);
}
performance.mark('markEnd'); // 标记一个结束点
performance.measure('measureVal', 'markStart', 'markEnd');
let measures = performance.getEntriesByName('measureVal');
let measure = measures[0]
console.log('milliseconds: ', measure.duartion); // 0.8999999990919605
// 清除标记
performance.clearMarks();
performance.clearMeasures();
上面就可以通过提供的API计算出for循环的执行时间。这几个API在性能监控应用比较频繁。
性能资源获取方法
performance.getEntries
获取所有资源请求的时间数据,这个函数返回一个按startTime排序的对象数组:

从上面可以看出,返回都是资源页面加载的相关数据,很多属性与performance.timing一样,在这就不再解释了。在这里梳理其他几个重要的属性:
name: 资源名称,是资源的绝对路径或调用mark方法自定义的名称(例如entryType为resource时,name表示资源的路径)。duration,一个DOMHighResTimeStamp对象,获取该资源消耗时长。startTime,一个DOMHighResTimeStamp对象,开始获取该资源的时间。entryType:
| 值 | 该类型对象 | 描述 |
|---|---|---|
mark |
PerformanceMark |
通过mark()方法添加到数组中的对象 |
measure |
PerformanceMeasure |
通过measure()方法添加到数组中的对象 |
paint |
PerformancePaintTiming |
值为first-paint首次绘制、first-contentful-paint首次内容绘制 |
resource |
PerformanceResourceTiming |
所有资源加载时间,用处最多 |
navigation |
PerformanceNavigationTiming |
现除chrome和Opera外均不支持,导航相关信息 |
frame |
PerformanceFrameTiming |
现浏览器均未支持 |
initiatorType,初始化该资源的资源类型:
| 发起对象 | 值 | 描述 |
|---|---|---|
a Element |
link/script/img/iframe等 |
通过标签形式加载的资源,值是该节点名的小写形式 |
a CSS resourc |
css |
通过css样式加载的资源,比如background的url方式加载资源 |
a XMLHttpRequest object |
xmlhttprequest/fetch |
通过xhr加载的资源 |
a PerformanceNavigationTiming object |
navigation |
当对象是PerformanceNavigationTiming时返回 |
performance.getEntriesByName
根据参数name,type获取一组当前页面已经加载的资源数据。name的取值对应到资源数据中的name字段,type取值对应到资源数据中的entryType字段。
let entries = window.performance.getEntriesByName(name, type);
performance.getEntriesByType
根据参数type获取一组当前页面已经加载的资源数据。type取值对应到资源数据中的entryType字段
let entries = window.performance.getEntriesByType(type);
getEntriesByName和getEntriesByType可以通过指定参数获取某类型的资源数据,这样我们可以资源数据分类统计,得出各类数据的情况。
上面就是performance的主要内容了,可以通过这个API做很多关于性能监控的事情,需要根据自己具体的场景制定合适的方案。熟悉Chrome开发者工具的同学,也是可以通过performance面板来做性能监控,那为啥还提供API来做呢,其实更多的希望通过打点统计数据量,可以通过可视化的方式对数据分析,更直观,进而做下一步操作。
结语
文章如有不正确的地方欢迎各位大佬指正,也希望有幸看到文章的同学也有收获,一起成长!
——本文首发于个人公众号———

最后,欢迎大家关注我的公众号,一起学习交流。
今天的文章应用:前端性能监控performance分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/17244.html