前端性能优化

104 阅读5分钟

总理念

优化什么?为什么优化?怎么优化?

优化什么

1. 指标设定

选择要设定的指标,看看那些需要优化,为什么优化,优化到什么标准是合格的,什么标准是不合格,优化以后能带来什么利益

1.1. 白屏

什么叫白屏时间呢?它指的是从输入内容回车(包括刷新、跳转等方式)后,到页面开始出现第一个字符的时间。这个过程包括 DNS 查询,建立 TCP 连接,发送首个HTTP请求(如果使用HTTPS还要介入 TLS 的验证时间),返回HTML文档,HTML文档 Head 解析完毕。它的标准时间是 300ms。

1.2. 首屏

首屏时间=白屏时间+渲染时间。它是指从浏览器输入地址并回车后,到首屏内容渲染完毕的时间。这期间不需要滚动鼠标或者下拉页面,否则无效。

1.3. 指标标准-分位值,时间值(1s),秒开率

分位值: 以 P99 为例,我们是把所有首屏时间排序,得出排在第 99 位的首屏时间就是 P99。

秒开率: 即 1s 内打开用户的占比

时间值: 如果一个站点对时间敏感,首屏时间在 1s 内,用户感觉会很快;如果首屏时间超过 2.5s,用户就会感觉很慢。但是在 1s 内打开页面,人们对这么短的时间并不敏感,体验不出 10ms 和 50ms 有什么差别。

1.4. 性能瓶颈的问题

客户端请求阶段的瓶颈点

客户端缓存(强缓存/协商缓存) dns解析(dns缓存) http请求 (6.1缓存原则)

服务端数据处理阶段的瓶颈点

数据缓存 资源压缩 重定向

页面解析和渲染阶段的瓶颈点

DOM 树 规范的dom树层级,有正确的闭合,否则时间会很长 jsde加载会阻页面的加载,导致页面加载缓慢 CSSOM树 合理的css布局可以减少重绘和重载,导致页面加载缓慢

1.5. 手段

场景一、弱网

  1. 合并请求,减少资源请求数量
  2. 图片暂时使用base64或者本地图片,或者使用占位符
  3. 资源的懒加载

场景二、首屏vue前端项目

  1. 利用MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。
  2. DOM 变化最大时对应的时间获取
  3. 图片加载完成最大的时间
function CScor(el, tiers, parentScore) {
    let score = 0;
    const tagName = el.tagName;
    if ("SCRIPT" !== tagName && "STYLE" !== tagName && "META" !== tagName && "HEAD" !== tagName) {
      const childrenLen = el.children ? el.children.length : 0;
      if (childrenLen > 0) for (let childs = el.children, len = childrenLen - 1; len >= 0; len--) {
        score += calculateScore(childs[len], tiers + 1, score > 0);
      }
      if (score <= 0 && !parentScore) {
        if (!(el.getBoundingClientRect && el.getBoundingClientRect().top < WH)) return 0;
      }
      score += 1 + .5 * tiers;
    }
    return score;
  }
function calFinallScore() {
    try {
       // 1. 一开始,函数检查了一个叫做sendMark的变量,如果为真,则函数立即返回,不执行后续代码。
      if (this.sendMark) return;
      // 2. 然后,函数计算了从页面开始获取(fetch)到当前的时间,并将其存储在变量time中。
      const time = Date.now() - performance.timing.fetchStart;
      // 3. isCheckFmp是一个布尔值,用于检查是否应该进行页面加载性能评分的计算。
      var isCheckFmp = time > 30000 || SCORE_ITEMS && SCORE_ITEMS.length > 4 && time - (SCORE_ITEMS && SCORE_ITEMS.length && SCORE_ITEMS[SCORE_ITEMS.length - 1].t || 0) > 2 * CHECK_INTERVAL || (SCORE_ITEMS.length > 10 && window.performance.timing.loadEventEnd !== 0 && SCORE_ITEMS[SCORE_ITEMS.length - 1].score === SCORE_ITEMS[SCORE_ITEMS.length - 9].score);
      // 4. 如果observer存在并且isCheckFmp为真,则断开observer与目标节点的连接,并将SCORE_ITEMS的副本存储到全局变量SCORE_ITEMS_CHART中。
      if (this.observer && isCheckFmp) {
      // 5. 然后,计算了fmps(一种用于测量网页加载完成时间的性能指标)。

        this.observer.disconnect();
        window.SCORE_ITEMS_CHART = JSON.parse(JSON.stringify(SCORE_ITEMS));
        let fmps = getFmp(SCORE_ITEMS);
        let record = null
        for (let o = 1; o < fmps.length; o++) {
          if (fmps[o].t >= fmps[o - 1].t) {
            let l = fmps[o].score - fmps[o - 1].score;
            (!record || record.rate <= l) && (record = {
              t: fmps[o].t,
              rate: l
            });
          }
        }
        //  然后,计算了fmps(一种用于测量网页加载完成时间的性能指标)。
        this.fmp = record && record.t || 30001;
        // 遍历fmps数组,找出评分变化最大的一项。
        // 在try-catch块中,函数检查了所有的图片并计算了最晚加载完成的图片的时间。
        try {
          this.checkImgs(document.body)
          let max = Math.max(...this.imgs.map(element => {
            if(/^(\/\/)/.test(element)) element = 'https:' + element;
            try {
              return performance.getEntriesByName(element)[0].responseEnd || 0
            } catch (error) {
              return 0
            }
          }))
          // 8. 如果record存在并且其时间t在合理范围内,则更新性能数据。
          record && record.t > 0 && record.t < 36e5 ? this.setPerformance({
            fmpImg: parseInt(Math.max(record.t , max))
          }) : this.setPerformance({});
        } catch (error) {
            // 9. 如果在上述过程中发生错误,则清空性能数据。
          this.setPerformance({});
          // console.error(error)
        }
      } else {
        // 10. 如果observer不存在或者isCheckFmp为假,则函数会在CHECK_INTERVAL时间后再次调用自身,直到满足条件为止。

        setTimeout(() => {
          this.calFinallScore();
        }, CHECK_INTERVAL);
      }
    } catch (error) {
      // 11. 最后,如果在函数执行过程中发生了任何未被捕获的错误,那么这些错误会被最外层的catch块捕获,但函数不会对这些错误做任何处理。
    }
  }

场景三、 白屏指标采集

html页面加载过程

客户端发起请求 -> 下载 HTML 及 JS/CSS 资源 -> 解析 JS 执行 -> JS 请求数据 -> 客户端解析 DOM 并渲染 -> 下载渲染图片-> 完成渲整体染。

白屏时间 = 页面开始展示时间点 - 开始请求时间点。

App下的白屏时间

初始化 WebView -> 客户端发起请求 -> 下载 HTML 及 JS/CSS 资源 -> 解析 JS 执行 -> JS 请求数据 -> 服务端处理并返回数据 -> 客户端解析 DOM 并渲染 -> 下载渲染图片 -> 完成整体渲染。

多了启动浏览器内核,也就是 Webview 初始化的时间。这个时间必须通过手动采集的方式来获得,而且因为线上线下时间差别不大,线下采集即可。具体来说,在 App 测试版本中,程序在 App 创建 WebView 时打一个点,然后在开始建立网络连接打一个点,这两个点的时间差就是 Webview 初始化的时间。

场景四、卡顿指标采集

FPS(Frames Per Second,每秒显示帧数) 连续 3 帧不低于 20 FPS,且保持恒定。

场景五、网络卡顿

github.com/lagoueduCol… image.png