项目上线后,一般都是都会作埋点,作数据监控、异常监控以及性能监控,本文主要聊聊前端性能监控。重点讲performance
怎么作性能监控。javascript
前端性能监控一般检测某些方面的加载时间,经过获得的加载时间的长短来判断项目某方面的性能怎样,具体是哪些方面呢???咱们先来看下页面加载是一个怎样的过程:css
页面加载的方式有两种,一种是加载完资源文件后经过javascript
动态获取接口数据,而后数据返回渲染内容,这就是先后端分离的项目页面加载方式。另外一种就是服务器渲染,同构直出前端页面,这种方式相对前一种方式页面加载的速度要快不少,性能方面相对也就要好些。可是目前大部分的项目都是先后端分离,本文重点描述这种方式。html
DNS
解析,将域名解析成IP
,若是缓存中存在,直接丛缓存中取IP
,不用作域名解析TCP
链接、三次握手:创建浏览器端和服务器端链接html
文件,构建DOM
树,CSSOM
树,js
文件的加载可能会阻塞页面的渲染html
被彻底加载和解析后会触发DOMContentLoaded
事件CSSOM
树和DOM
树构建完成后会开始生成Render
树,绘制Render
树的过程当中,浏览器就开始调用GPU
绘制,合成图层,将内容显示在屏幕上TCP
链接断开上面就是页面加载的基本过程,没有展开讲,若是要展开讲的话,十篇文章也讲不完,在本文中只须要清楚大概的整个过程,后期安排系统的整理上述整个过程的内容,作文字输出。前端
页面加载整个过程当中主要分为:白屏、重定向、DNS
查询、TCP
链接、HTTP
请求、DOM
解析、DOMready
、onload
等,这些也就是咱们前端性能监控包括的主要方面。咱们须要监控这几个方面的数据,作数据分析,进一步作前端性能优化。例如如何加快首屏加载时间、减小http
请求时间等等。vue
下面讲讲本文主角,performance
,不是Chrome
开发者工具的Performance
面板,固然Chrome
开发者工具也能作性能监控。java
performance
是前端性能监控的API
,能够获取到当前页面中也与性能相关的信息。git
咱们来看看天猫商城首页经过window.performance
这个API
,获取到的一些信息: github
window.performance
是一个对象,包含了四个属性:
memory
、
navigation
、
timeOrigin
、
timing
,以及一个事件处理程序
onresourcetimingbufferfull
,咱们再来看看这几个分别表明什么?
在Chrome
中添加的一个非标准扩展,memory
这个属性提供了一个能够获取到基本内存使用状况的对象MemoryInfo
chrome
performance.memory = {
jsHeapSizeLimit, // 内存大小限制,单位是字节B
totalJSHeapSize, // 可以使用的内存大小,单位是字节B
usedJSHeapSize // JS对象占用的内存大小,单位是字节B
}
复制代码
若是usedJSHeapSize
的值大于totalJSHeapSize
,会出现内存泄露的问题,因此不能大于totalJSHeapSize
的值。后端
返回PerformanceNavigation
对象,提供了在指定的时间段发生的操做相关信息,包括页面是加载仍是刷新、发生了多少重定向等。
performance.navigation = {
redirectCount: xxx,
type: xxx
}
复制代码
PerformanceNavigation
对象有两个属性,redirectCount
和type
。
只读属性,表示达到这个页面以前通过多少次重定向跳转,可是这个接口有同源策略的限制,仅能检测到同源的重定向。
只读属性,有四个返回值:0,1,2,225:
url
中直接输入地址,至关于常数performance.navigation.TYPE_NAVIGATE
过Location.reload()
方法显示的页面,至关于常数performance.navigation.TYPE_RELOAD
performance.navigation.TYPE_BACK_FORWARD
performance.navigation.TYPE_RESERVED
返回性能测量开始的时间的高精度时间戳。
一个回调的EventTarget
,当触发resourcetimingbufferfull
事件的时候会被调用。
返回PerformanceTiming
对象,包含了各类与浏览器性能相关的数据,提供了浏览器处理页面的各个阶段的耗时,其总体结构能够参考下图:
PerformanceTiming
对象中的属性都是只读属性,值都是精确到
Unix
毫秒的时间戳:
返回当前浏览器窗口的前一个页面的关闭,发生unload
事件时的时间戳。若是没有前一个页面,则等于fetchStart
属性。
返回若是前一个页面与当前页面同域,则返回前一个页面unload
事件发生时的时间戳。若是没有没有前一个页面,或者以前的页面跳转不是在同一个域名内,则返回值为0
和unloadEventStart
相对应,返回前一个页面unload
事件绑定的回调函数执行完毕的时间戳。若是没有没有前一个页面,或者以前的页面跳转不是在同一个域名内,则返回值为0
返回第一个http
重定向发生时的时间戳。有跳转而且是同域名内的重定向,不然返回值为0
返回最后一个http
重定向完成时的时间戳。有跳转而且是同域名内的重定向,不然返回值为0
返回浏览器准备好使用http
请求抓取文档的时间戳,这发生在检查本地缓存以前
返回DNS
域名查询开始的时间戳,若是使用了本地缓存(也就是没有作DNS
查询,直接从缓存中取到IP
)或者使用了持久链接,则与fetchStart
值相等
返回DNS
域名查询完成的时间戳,若是使用了本地缓存(也就是没有作DNS
查询,直接从缓存中取到IP
)或者使用了持久链接,则与fetchStart
值相等
返回http
(TCP
)开始创建链接的时间戳,若是是持久链接,则与fetchStart
值相等。若是在传输层发生了错误而且从新创建链接,则这里显示的是新创建的链接开始的时间戳
返回http
(TCP
)完成创建链接的时间戳,完成了四次握手,若是是持久链接,则与fetchStar
t值相等。若是在传输层发生了错误而且从新创建链接,则这里显示的是新创建的链接完成的时间戳。链接创建指的是全部握手和认证过程所有结束
返回https
链接开始的时间戳,若是不是安全链接,不然返回值为0
返回http
请求读取真实文档开始的时间戳(完成创建链接),包括从本地读取缓存。若是链接错误重连时,这里显示的也是新创建链接的时间戳
返回http
开始接收响应的时间戳(获取到第一个字节),包括从本地读取缓存
返回http
响应所有接收完成的时间戳(获取到最后一个字节),包括从本地读取缓存
返回开始解析渲染DOM
树的时间戳,此时Document.readyState
变为loading
,并将抛出readystatechange
相关事件
返回完成解析DOM
树的时间戳,Document.readyState
变为interactive
,并将抛出readystatechange
相关事件。这里只是DOM
树解析完成,这时候并无开始加载网页内的资源
返回DOM
解析完成后,网页内资源加载开始的时间戳。即全部须要被执行的脚本开始被解析了。在DOMContentLoaded
事件抛出前发生
返回DOM
解析完成后,网页内资源加载完成的时间戳。例如JS
脚本加载执行完成,不论执行顺序。DOMContentLoaded
事件也已经完成
返回DOM
解析完成,且资源也准备就绪的时间戳。Document.readyState
变为complete
,并将抛出readystatechange
相关事件
返回load
事件发送给文档,load
回调函数开始执行的时间戳。若是没有绑定load
事件,返回值为0
返回load
事件的回调函数执行完毕的时间戳。若是没有绑定load
事件,返回值为0
上面已经解释了相关属性的含义,经过上面的数据能作不少帮助咱们作性能监控的事情:
let performance = window.performance;
let t = performance.timing;
let time = t.loadEventEnd - t.navigationStart;
复制代码
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
也提供了一些方法,咱们来看看一些经常使用的方法:
建立一个DOMHighResTimeStamp
保存在资源缓存数据中,可经过performance.getEntries()
等相关接口获取。简单的理解就是能够作标记,也就是“打点”
performance.mark(name);
复制代码
用于清除标记,若是不加参数,就表示清除全部标记。
performance.clearMarks(name); // 清除指定标记
performance.clearMarks(); // 清除全部标记
复制代码
计算两个mark
之间的时长,建立一个DOMHighResTimeStamp
保存在资源缓存数据中,可经过performance.getEntries()
等相关接口获取。
performance.measure(name, startMark, endMark);
复制代码
移除缓存中全部entryType
为measure
的资源数据。
performance.clearMeasures(name); // 清除指定记录间隔数据
performance.clearMeasures(); // 清除全部记录间隔数据
复制代码
上面的四个API
,能够自定义统计一些数据,例如统计某函数的执行时间。
在Vue
中也有用到,为了追踪组件的性能,在Vue2.X
中全局配置API
有这么个方法:
Vue.config.performance = false;
复制代码
设置为true
以在浏览器开发工具的性能/时间线面板中启用对组件初始化、编译、渲染和打补丁的性能追踪。只适用于开发模式和支持performance.mark API
的浏览器上。咱们入口文件中开启,开启后可使用Vue Performance Devtool
这个chrom
e插件来查看各组件加载状况:
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
在性能监控应用比较频繁。
获取全部资源请求的时间数据,这个函数返回一个按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 时返回 |
根据参数name
,type
获取一组当前页面已经加载的资源数据。name
的取值对应到资源数据中的name
字段,type
取值对应到资源数据中的entryType
字段。
let entries = window.performance.getEntriesByName(name, type);
复制代码
根据参数type
获取一组当前页面已经加载的资源数据。type
取值对应到资源数据中的entryType
字段
let entries = window.performance.getEntriesByType(type);
复制代码
getEntriesByName
和getEntriesByType
能够经过指定参数获取某类型的资源数据,这样咱们能够资源数据分类统计,得出各种数据的状况。
上面就是performance
的主要内容了,能够经过这个API
作不少关于性能监控的事情,须要根据本身具体的场景制定合适的方案。熟悉Chrome
开发者工具的同窗,也是能够经过performance
面板来作性能监控,那为啥还提供API
来作呢,其实更多的但愿经过打点统计数据量,能够经过可视化的方式对数据分析,更直观,进而作下一步操做。
文章若有不正确的地方欢迎各位大佬指正,也但愿有幸看到文章的同窗也有收获,一块儿成长!
——本文首发于我的公众号———