前言
有一道题,对于每个前端程序员来说可能都不陌生,那就是:从输入url 到 页面加载 完成发生了什么? 先复习一下这道题:
从网上偷的图(1)
我们将这个过程切分为如下的过程片段:
DNS解析TCP连接HTTP请求抛出- 服务端处理请求,
HTTP响应返回 - 浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户
接下来我们性能优化的相关文章都将围绕这五个步骤进行
Performance
Performance 想必大家都听说过,可以mdn 上查一下相关完整的文字介绍和api介绍,我这里主要想结合浏览器页面加载的生命周期,把一些常用的指标给简单描述一下:
上图中的所有指标都可以在window.performance timing中查到
基于performance api 封装性能关键指标类
export class Performance {
public static readonly timing = window.performance && window.performance.timing
public static init() {
window.addEventListener('load', () => {
if (!this.timing) {
console.warn('当前浏览器不支持performance')
return
}
})
}
public static getTimings(){
if(!this.timing){
console.warn('浏览器不支持performance')
return {}
}
return {
redirect:this.getRedirectTiming(),
dns:this.getDnsTiming(),
tcp:this.getTcpTiming(),
ttfb:this.getTimeOfFirstByte(),
req:this.getRequestTime(),
ppdt:this.getParsePureDomTime(),
load:this.getLoadingTime(),
fpt:this.getFirstPaintTime()
}
}
// 重定向耗时
private static getRedirectTiming() {
return this.timing.redirectEnd - this.timing.redirectStart
}
// Dns解析耗时
private static getDnsTiming() {
return this.timing.domainLookupEnd - this.timing.domainLookupStart
}
// Tcp链接耗时
private static getTcpTiming() {
return this.timing.connectStart - this.timing.connectStart
}
// 读取页面第一个字节的时间
private static getTimeOfFirstByte() {
return this.timing.responseStart - this.timing.navigationStart
}
// 请求耗时
private static getRequestTime() {
return this.timing.responseEnd - this.timing.responseStart
}
// 解析纯dom时间
private static getParsePureDomTime() {
return this.timing.domInteractive - this.timing.domLoading
}
// 页面load的时间
private static getLoadingTime(){
return this.timing.loadEventStart - this.timing.navigationStart
}
// 页面首次绘制时间
private static getFirstPaintTime(){
return Math.round(
window.performance.getEntriesByName &&
window.performance.getEntriesByName('first-paint') &&
window.performance.getEntriesByName('first-paint')[0] &&
window.performance.getEntriesByName('first-paint')[0].startTime
)
}
}
简单封装性能上报类
import { v4 as uuid } from 'uuid';
import queryString from 'query-string';
let img: HTMLImageElement | null
export class Track {
private static server: string = 'xxxxxxxx' //这里写自己上报服务地址
public static report(params :{[key:string]:any}){
try{
const qs = queryString.stringify({
timestrap:Date.now(),
traceId:this.getTraceId(),
url:location.href,
...params
})
this.reportByImg(qs)
}catch(e){
console.log(e)
}
}
private static reportByImg(qs: string, reTryTimes: number = 3) {
const reTry = () => {
img = null
if (reTryTimes > 0) {
this.reportByImg(qs, reTryTimes - 1)
}
}
return new Promise((resolve, reject) => {
try {
img = new Image()
img.onerror = () => {
reTry()
}
img.src = this.server + qs
} catch (err) {
console.log(err)
}
})
}
private static getTraceId(){
try {
const trackKey = 'demo_key'
let traceId = localStorage.getItem(trackKey)
if(!traceId){
traceId = uuid()
localStorage.setItem(trackKey,traceId)
}
return traceId
}catch{
return ''
}
}
}
所以在performance类当中可以添加上报功能
export class Performance {
public static readonly timing = window.performance && window.performance.timing
public static init() {
window.addEventListener('load', () => {
if (!this.timing) {
console.warn('当前浏览器不支持performance')
return
}
// 上报指标
Track.report(this.getTimings())
})
}
public static getTimings(){
if(!this.timing){
console.warn('浏览器不支持performance')
return {}
}
return {
redirect:this.getRedirectTiming(),
dns:this.getDnsTiming(),
tcp:this.getTcpTiming(),
ttfb:this.getTimeOfFirstByte(),
req:this.getRequestTime(),
ppdt:this.getParsePureDomTime(),
load:this.getLoadingTime(),
fpt:this.getFirstPaintTime()
}
}
// 重定向耗时
private static getRedirectTiming() {
return this.timing.redirectEnd - this.timing.redirectStart
}
// Dns解析耗时
private static getDnsTiming() {
return this.timing.domainLookupEnd - this.timing.domainLookupStart
}
// Tcp链接耗时
private static getTcpTiming() {
return this.timing.connectStart - this.timing.connectStart
}
// 读取页面第一个字节的时间
private static getTimeOfFirstByte() {
return this.timing.responseStart - this.timing.navigationStart
}
// 请求耗时
private static getRequestTime() {
return this.timing.responseEnd - this.timing.responseStart
}
// 解析纯dom时间
private static getParsePureDomTime() {
return this.timing.domInteractive - this.timing.domLoading
}
// 页面load的时间
private static getLoadingTime(){
return this.timing.loadEventStart - this.timing.navigationStart
}
// 页面首次绘制时间
private static getFirstPaintTime(){
return Math.round(
window.performance.getEntriesByName &&
window.performance.getEntriesByName('first-paint') &&
window.performance.getEntriesByName('first-paint')[0] &&
window.performance.getEntriesByName('first-paint')[0].startTime
)
}
}
总结
上述是基于performance 简单实现一个性能指标监控上报的功能,但每个行业业务所关注的指标都不一样,所以可根据自己需求增加修改相关指标,之后的文章会写一些通用的优化方法,指标篇暂时就写这些了,下期见!