性能监控是性能优化的第一步,至关重要,因为我们只有进行了性能监控才能知道性能的瓶颈所在,最后对症下药进行性能的优化。
关于Performance.timing
支持情况:目前,所有主要浏览器都已经支持performance对象,包括Chrome 20+、Firefox 15+、IE 10+、Opera 15+。
-
API
window.performance.timing
从图中我们可以看到该API给了我们很多数据,我们可以使用这些数据对性能进行监控。
// 上报函数
function send(data) {
// 发起ajax请求
}
function handleData(performance) {
let performanceData = {}
if (performance) {
// 重定向时间
performanceData.redirectTime = performance.redirectEnd - performance.redirectStart
// 缓存时间
performanceData.cacheTime = performance.domainLookupStart - performance.fetchStart
// dns查询时间
performanceData.dnsTime = performance.domainLookupEnd - performance.domainLookupStart
// tcp握手时间
performanceData.TcpTime = performance.connectEnd - performance.connectStart
// ajax请求时间
performanceData.ajaxTime = performance.responseEnd - performance.requestStart
// 开始解析dom时间,此时document.readyState 变为 loading
performanceData.domLoadingTime = performance.domLoading - performance.navigationStart
// dom解析完成时间,此时document.readyState 变为 interactive
performanceData.domInteractiveTime = performance.domInteractive - performance.navigationStart
// dom解析完成,资源加载完成,脚本完成
performanceData.domContentLoadedEventEndTime = performance.domContentLoadedEventEnd - performance.navigationStart
// 页面从开始到结束的全部时间时间
performanceData.loadPageTime = performance.loadEventStart - performance.navigationStart
}
return performanceData
}
// 初始化函数
function init() {
window.onload = function () {
if (window.performance) {
let timing = window.performance.timing;
let performanceData = handleData(timing)
performanceData.timestamp = Date.now()
performanceData.url = location.href
send(performanceData)
}
}
}
let performanceMonitor = {
init
}
export default performanceMonitor
关于Performance.getEntries() 获取所有资源请求的时间数据
这个函数返回的将是一个数组,包含了页面中所有的 HTTP 请求
function getAllSourceTime() {
let allSourceTime = []
if (window.performance && window.performance.getEntries) {
window.performance.getEntries().forEach(function (item) {
let temp = {}
temp.name = item.name
temp.entryType = item.entryType
if (item.entryType === 'paint') {
temp.startTime = item.startTime
} else {
temp.transferSize = item.transferSize
temp.duration = item.duration
temp.initiatorType = item.initiatorType
}
allSourceTime.push(temp)
})
}
return allSourceTime
}
返回的数组中有两个东西需要我们注意,一个是first-paint(首次绘制),另一个是first-contentful-paint(首次内容绘制)。first-paint表示浏览器绘制了页面的第一个像素的那一刻,first-contentful-paint表示第一 bit 的内容被绘制的那一刻。
但实际上我们有的时候并不需要所有资源的请求时间数据,我们只关心first-paint和first-contentful-paint的时间,因此我们可以把上述两种方法结合一下,让我们的监控数据变得有重点。
// 获取first-paint和first-contentful-paint的时间
function getPaintTime() {
let obj = {}
if (window.performance && window.performance.getEntriesByType) {
let paintArr = window.performance.getEntriesByType('paint')
if (paintArr && paintArr.length) {
paintArr.forEach(function (item) {
obj[item.name] = item.startTime
})
}
}
return obj
}
// 初始化函数
function init() {
window.onload = function () {
if (window.performance) {
let timing = window.performance.timing;
let performanceData = handleData(timing)
performanceData.timestamp = Date.now()
performanceData.url = location.href
performanceData = Object.assign({}, performanceData, getPaintTime())
send(performanceData)
}
}
}
关于PerformanceObserver
function performanceObserver() {
let obj = {}
var observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
if (entry.entryType === 'paint') {
obj[entry.name] = entry.startTime
} else {
let temp = handleData(entry)
obj = Object.assign({}, obj, temp)
}
})
});
observer.observe({ entryTypes: ['paint', 'navigation'] });
return obj
}
performanceObserver()
对比与融合
经过试验我发现,PerformanceObserver是使用一种观察者模式去实时的取到那些数据值,而Performance.timing必须放在window.onload函数里面进行取值。这些差异导致会有一些小问题产生。
问题一:在window.onload函数里面我们进行loadEventEnd的取值会取不到,而在PerformanceObserver则不存在这样的问题;
问题二:使用PerformanceObserver我们发现没有navigationStart,domLoading的值。
问题三:PerformanceObserver更精确。
所以综合它们,最终我们的代码是这样的:
// 上报函数
function send(data) {
// 发起ajax请求
}
// 处理数据
function handleData(performance) {
let navigationStart = performance.navigationStart || performance.fetchStart
let performanceData = {}
if (performance) {
// 重定向时间
performanceData.redirectTime = performance.redirectEnd - performance.redirectStart
// 缓存时间
performanceData.cacheTime = performance.domainLookupStart - performance.fetchStart
// dns查询时间
performanceData.dnsTime = performance.domainLookupEnd - performance.domainLookupStart
// tcp握手时间
performanceData.TcpTime = performance.connectEnd - performance.connectStart
// ajax请求时间
performanceData.ajaxTime = performance.responseEnd - performance.requestStart
// 开始解析dom时间,此时document.readyState 变为 loading
performanceData.domLoadingTime = performance.domLoading ? performance.domLoading - navigationStart : null
// dom解析完成时间,此时document.readyState 变为 interactive
performanceData.domInteractiveTime = performance.domInteractive - navigationStart
// dom解析完成,资源加载完成,脚本完成
performanceData.domContentLoadedEventEndTime = performance.domContentLoadedEventEnd - navigationStart
// 页面从开始到结束的全部时间时间
performanceData.loadPageTime = performance.loadEventEnd ? performance.loadEventEnd - navigationStart : null
}
return performanceData
}
// 获取first-paint及first-contentful-paint的时间
function getPaintTime() {
let obj = {}
if (window.performance && window.performance.getEntriesByType) {
let paintArr = window.performance.getEntriesByType('paint')
if (paintArr && paintArr.length) {
paintArr.forEach(function (item) {
obj[item.name] = item.startTime
})
}
}
return obj
}
// performanceObserver进行监控
function performanceObserver() {
let obj = {}
var observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
if (entry.entryType === 'paint') {
obj[entry.name] = entry.startTime
} else {
let temp = handleData(entry)
obj = Object.assign({}, obj, temp)
}
})
obj.from = 'window.PerformanceObserver'
obj.url = location.href
obj.timestamp = Date.now()
send(obj)
});
observer.observe({ entryTypes: ['navigation', 'paint'] });
}
// 初始化函数
function init() {
if (window.PerformanceObserver) {
performanceObserver()
} else if (window.performance) {
window.onload = function () {
let timing = window.performance.timing;
let performanceData = handleData(timing)
performanceData.timestamp = Date.now()
performanceData.url = location.href
performanceData.from = 'window.performance'
performanceData = Object.assign({}, performanceData, getPaintTime())
send(performanceData)
}
}
}
let performanceMonitor = {
init
}
export default performanceMonitor