前端性能优化——性能优化性能指标篇(一)

90 阅读2分钟

前言

有一道题,对于每个前端程序员来说可能都不陌生,那就是:从输入url 到 页面加载 完成发生了什么? 先复习一下这道题:

image.png

从网上偷的图(1)

我们将这个过程切分为如下的过程片段:

  1. DNS 解析
  2. TCP 连接
  3. HTTP 请求抛出
  4. 服务端处理请求,HTTP 响应返回
  5. 浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户
    接下来我们性能优化的相关文章都将围绕这五个步骤进行

Performance

Performance 想必大家都听说过,可以mdn 上查一下相关完整的文字介绍和api介绍,我这里主要想结合浏览器页面加载的生命周期,把一些常用的指标给简单描述一下:

浏览器解析过程.jpeg 上图中的所有指标都可以在window.performance timing中查到 image.png

基于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 简单实现一个性能指标监控上报的功能,但每个行业业务所关注的指标都不一样,所以可根据自己需求增加修改相关指标,之后的文章会写一些通用的优化方法,指标篇暂时就写这些了,下期见!