作者:韩小窝
https://mp.weixin.qq.com/s/wJxj5QbOHwH9cKmqU5eSQw

使用Performance API获取性能相关指标

接下来我们来了解一下目前常用的性能指标,并且我们需要知道其中一些关键指标如何用Performance API获取。

TTFB 首字节时间

TTFB(Time To First Byte): 从发送请求到数据返回第一个字节所消耗的时间(首字节

  1. const { responseStart, requestStart } = performance.timing
  2. const TTFB = responseStart - requestStart

FP 首次绘制

FP (First Paint) 首次绘制: 第一个像素绘制到页面上的时间

  1. const paint = performance.getEntriesByType('paint')
  2. const FP = paint[0].startTime

FCP 首次内容绘制

FCP (First Contentful Paint) 首次内容绘制 标记浏览器渲染来自 DOM 第一位内容的时间点,该内容可能是文本、图像、SVG 甚至 元素.

  1. const paint = performance.getEntriesByType('paint')
  2. const FCP = paint[1].startTime

FMP 首次有效绘制

FMP(First Meaningful Paint) 首次有效绘制: 例如,在 YouTube 观看页面上,主视频就是主角元素.
图片可以没加载完成,但整体的骨架已经加载完成了。
1秒内完成FMP的概率超过80%,那就代表这个网站是一个性能较好的网站

  1. let FMP = 0
  2. const performanceObserverFMP = new PerformanceObserver((entryList, observer) => {
  3. const entries = entryList.getEntries()
  4. observer.disconnect()
  5. FMP = entries[0].startTime
  6. })
  7. // 需要在元素中添加 elementtiming="meaningful"
  8. performanceObserverFMP.observe({ entryTypes: ['element'] })

TTI 可交互时间

TTI (Time to Interactive) 可交互时间: DOM树构建完毕,可以绑定事件的时间

  1. const { domInteractive, fetchStart } = performance.timing
  2. const TTI = domInteractive - fetchStart

LCP 最大内容渲染

LCP (Largest Contentful Paint) 最大内容渲染: 代表在viewport中最大的页面元素加载的时间. LCP的数据会通过PerformanceEntry对象记录, 每次出现更大的内容渲染, 则会产生一个新的PerformanceEntry对象.(2019年11月新增)

  1. let LCP = 0
  2. const performanceObserverLCP = new PerformanceObserver((entryList, observer) => {
  3. const entries = entryList.getEntries()
  4. observer.disconnect()
  5. LCP = entries[entries.length - 1].startTime
  6. })
  7. performanceObserverLCP.observe({ entryTypes: ['largest-contentful-paint'] })

DCL

DCL (DomContentloaded): 当 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,无需等待样式表、图像和子框架的完成加载

  1. const { domContentLoadedEventEnd, fetchStart } = performance.timing
  2. const DCL = domContentLoadedEventEnd - fetchStart

L 全部加载完毕

L (onLoad), 当依赖的资源(图片、文件等), 全部加载完毕之后才会触发

  1. const { loadEventStart, fetchStart } = performance.timing
  2. const L = loadEventStart - fetchStart

FID 首次输入延迟

FID (First Input Delay) 首次输入延迟: 指标衡量的是从用户首次与您的网站进行交互(即当他们单击链接,点击按钮等)到浏览器实际能够访问之间的时间

  1. let FID = 0
  2. const performanceObserverFID = new PerformanceObserver((entryList, observer) => {
  3. const entries = entryList.getEntries()
  4. observer.disconnect()
  5. FID = entries[0].processingStart - entries[0].startTime
  6. })
  7. performanceObserverFID.observe({ type: ['first-input'], buffered: true })

TBT 页面阻塞总时长

TBT (Total Blocking Time) 页面阻塞总时长: TBT汇总所有加载过程中阻塞用户操作的时长,在FCP和TTI之间任何long task中阻塞部分都会被汇总

CLS 累积布局偏移

CLS (Cumulative Layout Shift) 累积布局偏移: 总结起来就是一个元素初始时和其hidden之间的任何时间如果元素偏移了, 则会被计算进去, 具体的计算方法可看这篇文章 https://web.dev/cls/

SI

SI (Speed Index): 指标用于显示页面可见部分的显示速度, 单位是时间

Coverage(覆盖率)

获取代码未使用占比
2022/05/23【Web页面全链路性能优化指南2】 - 图1

Lighthouse

获取性能报告并查看推荐优化项
可以在本地安装命令行工具来使用,也可以通过Chrome来使用。
命令行方式使用

  1. npm install -g lighthouse
  2. lighthouse --view https://m.baidu.com


在Chrome中使用
2022/05/23【Web页面全链路性能优化指南2】 - 图2
2022/05/23【Web页面全链路性能优化指南2】 - 图3

Network(网络)

网络请求中的Timing(时间)

能获取网络请求的时间消耗细节,可以根据耗时来决定优化策略。优先优化耗时最长的2022/05/23【Web页面全链路性能优化指南2】 - 图4
【正在排队】网络请求队列的排队时间
【已停止】阻塞住用于处理其他事情的时间
【DNS查找】用于DNS解析IP地址的时间
【初始连接】创建TCP连接时间
【SSL】用于SSL协商的时间
【已发送请求】用于发送请求的时间
【等待中】请求发出至接收响应的时间也可以理解为服务端处理请求的时间
【下载内容】下载响应的时间

网络请求的优先级

浏览器会根据资源的类型决定优先请求哪些资源,优先级高的请求能够优先被加载。
2022/05/23【Web页面全链路性能优化指南2】 - 图5
右击此处勾选优先级可打开优先级功能,在请求中便可看到网络请求的优先级
2022/05/23【Web页面全链路性能优化指南2】 - 图6
不同资源类型的优先级排序如下
【最高】html、style
【高】font、fetch、script
【低】image、track

网页总资源信息

2022/05/23【Web页面全链路性能优化指南2】 - 图7
【58个请求】网页一共多少个请求
【6.9 MB 项资源】网页资源一共6.9MB大小
【DOMContentLoaded:454 毫秒】DOM加载完毕的时长
【加载时间:1.02 秒】onload完毕的时长

Network配置

2022/05/23【Web页面全链路性能优化指南2】 - 图8

网页性能优化

上面我们分别讲解了进程与线程、浏览器打开一个页面的完整过程、浏览器处理每一帧时的流程、Chrome性能相关的各种工具以及性能相关的各种指标。以上内容都掌握之后我们再考虑性能优化遍有了思路,我们在页面展示的任意一个步骤中对其进行优化都能对整个网页的展示性能产生影响。
下面列出了一个页面能优化的所有内容,读者可根据自己的业务情况结合性能工具来做适合自己的优化方式。

网络优化策略

减少HTTP请求数

合并JS、合并CSS、合理内嵌JS和CSS、使用雪碧图

使用HTTP缓存

使用强制缓存可以不走网络请求,直接走本地缓存数据来加载资源。
使用协商缓存可以减少数据传输,当不需要更新数据时可通知客户端直接使用本地缓存。

使用 HTTP/2.0

HTTP/2.0使用同一个TCP连接来发送数据,他把多个请求通过二进制分贞层实现了分贞,然后把数据传输给服务器。也叫多路复用,多个请求复用同一个TCP连接。
HTTP/2.0会将所有以:开头的请求头做一个映射表,然后使用hpack进行压缩,使用这种方式会使请求头更小。
服务器可主动推送数据给客户端。

避免重定向

301、302 重定向会降低响应速度

使用 dns-prefetch

DNS请求虽然占用的带宽较少,但会有很高的延迟,由其在移动端网络会更加明显。
使用dns-prefetch可以对网站中使用到的域名提前进行解析。提高资源加载速度。
通过dns预解析技术可以很好的降低延迟,在访问以图片为主的移动端网站时,使用DNS预解析的情意中下页面加载时间可以减少5%。

  1. <link rel="dns-prefetch" href="https://a.hagan.com/">

使用域名分片

在HTTP/1.1中,一个域名同时最多创建6个TCP连接,将资源放在多个域名下可提高请求的并发数

CDN

静态资源全上CDN,CDN能非常有效的加快网站静态资源的访问速度。

压缩

gzip压缩、html压缩、js压缩、css压缩、图片压缩

使用contenthash

contenthash可以根据文件内容在文件名中加hash,可用于浏览器缓存文件,当文件没有改变时便直接取本地缓存数据

合理使用preload、prefetch

preload预加载、prefetch空闲时间加载

  1. <link rel="preload" as="style" href="/static/style.css">
  2. <link rel="preload" as="font" href="/static/font.woff">
  3. <link rel="preload" as="script" href="/static/script.js">
  4. <link rel="prefetch" as="style" href="/static/style.css">
  5. <link rel="prefetch" as="font" href="/static/font.woff">
  6. <link rel="prefetch" as="script" href="/static/script.js">

两者都不会阻塞onload事件,prefetch 会在页面空闲时候再进行加载,是提前预加载之后可能要用到的资源,不一定是当前页面使用的,preload 预加载的是当前页面的资源。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <link rel="preload" as="style" href="./preload.css">
  8. <title>Document</title>
  9. </head>
  10. <body>
  11. <article></article>
  12. </body>
  13. </html>

浏览器渲染优化策略

关键渲染路径

当通过JS或者其他任意方式修改DOM后,浏览器会进入如下流程
【JS通过API修改DOM】>【计算样式】>【布局(重排)】>【绘制(重绘)】>【合成】
Reflow 重排:重排在Chrome Performance中叫做布局,通常添加或删除元素、修改元素大小、移动元素位置、获取位置信息都会触发页面的重排,因为重排可能会改变元素的大小位置等信息,这样的改变会影响到页面大量其它元素的大小位置信息,会耗费掉大量的性能,所以在实际应用中我们应该尽可能的减少重排
Repaint 重绘:重绘在Chrome Performance中叫做绘制,通常样式改变但没有影响位置时会触发重绘操作,重绘性能还好,但我们也需要尽量减少重绘,如果需要做一些动画,我们尽量使用CSS3动画,CSS3动画只需要在初始化时绘制一次,之后的动画都不会触发重绘操作。

强制同步布局问题

在同一个函数内,修改元素后又获取元素的位置时会触发强制同步布局,影响渲染性能
强制同步布局会使js强制将【计算样式】和【布局(重排)】操作提前到当前函数任务中,这样会导致每次运行时执行一次【计算样式】和【重排】,这样一定会影响页面渲染性能,而正常情况下【计算样式】和【重排】操作会在函数结束后统一执行。

如何减少重排与重绘

  1. 脱离文档流(绝对定位、固定定位),脱离文档流的元素进行重排不会影响到其他元素。
  2. 图片渲染时增加宽高属性,宽高固定后,图片不会根据内容动态改变高度,便不会触发重排。
  3. 尽量用CSS3动画,CSS3动画能最大程度减少重排与重绘。
  4. 使用will-change: transform;将元素独立为一个单独的图层。(定位、透明、transform、clip都会产生独立图层)

    静态文件优化策略

    图片格式

    jpeg

    适合色彩丰富的图、Banner图。不适合:图形文字、图标、不支持透明度。

    png

    适合纯色、透明、图标,支持纯透明和半透明。不适合色彩丰富图片,因为无损储存会导致储存体积大于jpeg

    gif

    适合动画、可以动的图标。支持纯透明但不支持半透明,不适合色彩丰富的图片。
    埋点信息通常也会使用gif发送,因为1x1的gif图发送的网络请求比普通的get请求要小一些。

    webp

    支持纯透明和半透明,可以保证图片质量和较小的体积,适合Chrome和移动端浏览器。不适合其他浏览器。

    svg

    矢量格式,大小非常小,但渲染成本过高,适合小且色彩单一的图标。

    图片优化

  • 减少图片资源的尺寸和大小,节约用户流量
  • 设置alt=”xxx”属性,图像无法显示时会显示alt内容
  • 图片懒加载, loading=”lazy”为原生,建议使用IntersectionObserver自己做懒加载
  • 不同环境加载不同尺寸和像素的图片srcset与sizes的使用。
  • 采用渐进式加载 先加载占位图,然后加载模糊小图,最后加载真正清晰的图
  • 使用Base64URL 减少图片请求数
  • 采用雪碧图合并图片,减少请求数。

    HTML优化

  • 语义化HTML,代码简洁清晰,利于SEO,便于开发维护。

  • 减少HTML嵌套关系,减少DOM节点数量。
  • 提前声明字符编码,让浏览器快速确定如何渲染网页内容
  • 删除多余空格、空行、注释、无用属性
  • 减少iframe,子iframe会阻塞父级的onload事件。可以使用js动态给iframe赋值,就能解决这个问题。
  • 避免table布局

    CSS优化

  • 减少伪类选择器,减少选择器层数、减少通配符选择器、减少正则选择器

  • 避免css表达式background-color: expression(…)
  • 删除空格、空行、注释、减少无意义的单位、css压缩
  • css外链,能走缓存
  • 添加媒体字段,只加载有效的css文件使用css contain属性,能控制对应元素是否根据子集元素的改变进行重排
  • 减少@import使用,因为它使用串行加载

    JS优化

  • 通过script的async、defer属性异步加载,不阻塞DOM渲染

  • 减少DOM操作,缓存访问过的元素。
  • 不直接操作真实DOM,可以先修改,然后一次性应用到DOM上。(虚拟DOM、DOM碎片节点)
  • 使用webworker解决复杂运算,避免复杂运算阻塞主线程,webworker线程位于渲染进程
  • 图片懒加载,使用IntersectionObserver实现虚拟滚动
  • 使用requestAnimationFrame来做动画,使用requestIdleCallback来进行空闲时的任务处理
  • 尽量避免使用eval,性能差。
  • 使用事件委托,能减少事件绑定个数。事件越多性能越差。
  • 尽量使用canvas、css3动画。
  • 通过chrome覆盖率(Coverage)工具排查代码中未使用过的代码并将其删除
  • 通过chrome性能(Performance)工具查看每个函数的执行性能并优化

    字体优化

    FOUT(Flash of Unstyled Text)等待一段时间,如果没加载完成,先显示默认。加载 后再进行切换。
    FOIT(F1ash of Invisib1e Text) 字体加载完毕后显示,加载超时降级系统字体(白 屏

    浏览器储存优化策略

    Cookie

    cookie在过期之前一直有效,最大储存大小为4k,限制字段个数,不适合大量的数据储存,每次请求会携带cookie,主要用来做身份校验。
    优化方式:
  1. 需要合理设置cookie有效期
  2. 根据不同子域划分cookie来减少cookie传输
  3. 静态资源域名和cookie域名采用不同域名,避免静态资源请求携带cookie。

    LocalStorage

    Chrome下最多储存5M,除非手动清除,否则一直存在。可以利用localStorage储存静态资源。比如储存网页的.js、.css,这样会使页面打开速度非常快。例如 https://m.baidu.com

    SessionStorage

    会话级别储存,可用于页面间的传值

    IndexDB

    浏览器的本地数据库,大小几乎无上限

    其他优化策略

  • 关键资源个数越多,首次页面加载时间就会越长
  • 关键资源的大小,内容越小下载时间越短。
  • 优化白屏,合理使用内联css、js
  • 预渲染,打包时进行预渲染,生成静态HTML文件,用户访问时直接返回静态HTML。
  • 服务端渲染同构,加速首屏速度(耗费服务端资源),有利于SEO优化。首屏使用服务端渲染,后续交互使用客户端渲染。

    使用PWA提高用户体验

    webapp用户体验差的一大原因是不能离线访问。用户粘性低的一大原因是无法保存入口,PWA就是为了解决webapp的用户体验问题而诞生的。使用PWA能令站点拥有快速、可靠、安全等特性。
  1. Web App Manifest 将网站添加到电脑桌面、手机桌面,类似Native的体验。
  2. Service Worker 配合Cache API,能做到离线缓存各种内容。
  3. Push API 配合 Notification API,能做到类似Native的消息推送与实时提醒。
  4. App Shell 配合 App Skeleton,能做App壳与骨架屏