Skip to content

性能优化

参考文章

🌟 WHAT

如不考虑网络问题,客户端的性能指标按照(先后顺序)大致分为以下几

  • 首次内容绘制:白屏时间,受网络、入口页的 DOM 节点数量共同影响

  • 最早可交互时间:受 DOM 节点数量影响,React 同步模式下,交互被长任务阻塞

  • 总阻塞时间:从首次内容绘制到最早可交互的总时间,受 React 的 commit 流程影响,交互被长任务阻塞

  • 最大内容绘制:页面中的所有元素都渲染完毕的时间

  • 视觉稳定性:受回流的影响,以及图片加载导致的偏移

🌟 网页性能评判方式

Performance

时间节点数据(核心)

几个关键节点:

  • requestStart:http 在建立链接之后,正式开始请求真实文档的时间,包括从本地读取缓存

  • domLoading:开始解析渲染 DOM 树的时间

  • domInteractive:完成解析 DOM 树的时间

  • domContentLoadedEventEnd:DOM 解析完成后,网页内资源加载完成的时间(如 js 脚本加载执行完)

  • loadEventStart:load 事件发送给文档,即 load 函数开始执行时

  • loadEventEnd:load 函数执行完毕的时间

然后是重要指标:

FP:白屏时间,从发起请求到页面渲染第一个像素点

FCP:首次内容绘制,

LCP:最大内容渲染时间。

重定向的数量

如果页面进入时发生了多次的跳转,就会导致用户等待的时间变成,体验较差

可以通过 navigation 面板判断:

    1. redirectCount,重定向的数量,经过多少次重定向进入这个页面 (注:这个页面有同源限制,只能统计同源情况下)
    1. type,进入页面的方式

具体实现

可以通过 window.performance 自己封装监控函数

这样做的原因是第三方的标准也是千奇百怪的,自己封装更容易适应自身的业务需要

js
const navigationType = {
  0: '正常进入非刷新,非重定向',
  1: '通过刷新的方式进入',
  2: '通过前进回退按钮进入',
  255: '非正常进入,非刷新,非前进回退进入'
}
let Performance = window.performance
let timing = Performance.timing
let navigation = Performance.navigation
let memory = Performance.memory
let PerformanceObj = {
  timing: {},
  navigation: {}
} // 性能监控对象
if (timing) {
  PerformanceObj['timing']['上一页面的卸载耗时'] =
    timing.unloadEventEnd - timing.navigationStart
  PerformanceObj['timing']['重定向耗时'] =
    timing.redirectEnd - timing.redirectStart
  PerformanceObj['timing']['查询appDNS缓存耗时'] =
    timing.domainLookupStart - timing.fetchStart
  PerformanceObj['timing']['DNS查询耗时'] =
    timing.domainLookupEnd - timing.domainLookupStart
  PerformanceObj['timing']['TCP连接建立耗时'] =
    timing.connectEnd - timing.connectStart
  PerformanceObj['timing']['服务器响应耗时'] =
    timing.responseStart - timing.requestStart // 发起请求到响应第一个字节
  PerformanceObj['timing']['request请求耗时'] =
    timing.responseEnd - timing.responseStart // 响应第一个字节到响应最后一个字节
  PerformanceObj['timing']['总耗时'] =
    (timing.loadEventEnd ||
      timing.loadEventStart ||
      timing.domComplete ||
      timing.domLoading) - timing.navigationStart
  PerformanceObj['timing']['解析dom树耗时'] =
    timing.domComplete - timing.responseEnd
}
if (navigation) {
  PerformanceObj['navigation']['重定向次数'] = navigation.navigation || 0
  PerformanceObj['navigation']['进入页面方式'] =
    navigationType[navigation.type] || '进入页面方式加载异常'
}
if (memory) {
  setInterval(() => {
    console.log(memory.jsHeapSizeLimit, memory.totalJSHeapSize)
  }, 300)
}
const navigationType = {
  0: '正常进入非刷新,非重定向',
  1: '通过刷新的方式进入',
  2: '通过前进回退按钮进入',
  255: '非正常进入,非刷新,非前进回退进入'
}
let Performance = window.performance
let timing = Performance.timing
let navigation = Performance.navigation
let memory = Performance.memory
let PerformanceObj = {
  timing: {},
  navigation: {}
} // 性能监控对象
if (timing) {
  PerformanceObj['timing']['上一页面的卸载耗时'] =
    timing.unloadEventEnd - timing.navigationStart
  PerformanceObj['timing']['重定向耗时'] =
    timing.redirectEnd - timing.redirectStart
  PerformanceObj['timing']['查询appDNS缓存耗时'] =
    timing.domainLookupStart - timing.fetchStart
  PerformanceObj['timing']['DNS查询耗时'] =
    timing.domainLookupEnd - timing.domainLookupStart
  PerformanceObj['timing']['TCP连接建立耗时'] =
    timing.connectEnd - timing.connectStart
  PerformanceObj['timing']['服务器响应耗时'] =
    timing.responseStart - timing.requestStart // 发起请求到响应第一个字节
  PerformanceObj['timing']['request请求耗时'] =
    timing.responseEnd - timing.responseStart // 响应第一个字节到响应最后一个字节
  PerformanceObj['timing']['总耗时'] =
    (timing.loadEventEnd ||
      timing.loadEventStart ||
      timing.domComplete ||
      timing.domLoading) - timing.navigationStart
  PerformanceObj['timing']['解析dom树耗时'] =
    timing.domComplete - timing.responseEnd
}
if (navigation) {
  PerformanceObj['navigation']['重定向次数'] = navigation.navigation || 0
  PerformanceObj['navigation']['进入页面方式'] =
    navigationType[navigation.type] || '进入页面方式加载异常'
}
if (memory) {
  setInterval(() => {
    console.log(memory.jsHeapSizeLimit, memory.totalJSHeapSize)
  }, 300)
}

内存占用

  • jsHeapSizeLimit:页面最多可以获得的 JavaScript 堆的大小

  • totalJSHeapSize:已分配的 JavaScript 堆的大小

  • usedJSHeapSize:已使用的 JavaScript 堆的大小

注意:totalJSHeapSize > jsHeapSizeLimit,就会触发页面崩溃,有内存泄露的风险

附加规范(社区标准)

首次内容绘制(FCP)

测量页面从开始加载页面到页面内容的任何部分在屏幕上完成渲染的时间。

这里强调的是部分内容,并非所有内容。

评判标准:

0-1.8s 良好

1.9s-3.0s 需要改进

3.1s 以上 较差

此指标对于没有使用 SSR 技术的项目意义不大,因为通常需要等待 JS 加载完毕才会开始绘制

最大内容绘制(LCP)

根据页面首次开始加载的时间点来计算可视区域内可见的最大内容块完成渲染的相对时间。

评判标准:

0-2.5s 良好

2.6s-4.0s 需要改进

4.1s 以上 较差

总阻塞时间(TBT)

测量首次内容绘制(FCP) 与可交互时间(TTI)之间的总时间。

0-0.2s 良好

0.2s-0.6s 需要改进

0.6s 以上 较差

累积布局偏移(CLS)

测量整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移分数。

是测量视觉稳定性的一个重要指标,确保页面是令人愉悦的。

速度指标(SI)

表示页面可视区域中内容的填充速度的指标。

捕获页面出现像素点的时间。

评判标准:

0-3.4s 良好

3.4s-5.8s 需要改进

5.8s 以上 较差

性能优化的常用方法

客户端

  1. link 标签提前,script 标签置后(避免 js 运行导致 html 渲染阻塞)

  2. 对于引入三方的 scriptlink 标签加上 asyncdefer

  3. 资源 dns 预解析

  4. 代码体积压缩和树摇

  5. 资源预连接,例如<link rel="preconnect" />,提前通知浏览器与另一个源建立连接

  6. 代码分割和动态加载、按需引入,减少首屏请求的资源

服务端

  1. 减少服务器的响应时间,例如使用 HTTP2 提高性能

  2. 使用接口缓存:强缓存或协商缓存

  3. 压缩图片、图片资源走 CDN、懒加载

  4. 启用 gzip 压缩,进一步压缩代码体积