常见的性能优化方案

对于前端应用来说,页面加载耗时、渲染耗时、网络耗时、脚本执行耗时等指标会影响用户的等待时长,而 CPU 占用、内存占用、本地缓存占用等可能会导致页面卡顿甚至卡死。因此,性能优化可以分别从常见耗时资源占用两方面来解决。

时间优化角度: 减少耗时

首先看一下浏览器页面加载过程

  1. 网络请求,服务端返回HTML内容
  2. 浏览器一边解析HTML,一边进行页面渲染
  3. 解析到外部资源,会发起HTTP请求获取,加载 JavaScript 代码时会暂停页面渲染
  4. 根据业务代码加载过程,会分别进入页面开始渲染、渲染完成、用户可交互等阶段
  5. 页面交互过程中,会根据业务逻辑进行逻辑运算、页面更新

根据这个过程,我们可以从4个方面进行耗时优化:网络请求优化首屏加载优化渲染过程优化计算/逻辑运行提速

1. 网络请求优化

减少网络资源的请求和加载耗时

  • 减少 DNS 查询时间,比如使用浏览器 DNS 缓存,计算机 DNS 缓存,服务器 DNS 缓存(DNS的查询过程?)
  • 合理利用 CDN,有效减少网络请求耗时
  • 对请求资源进行缓存,比如使用浏览器缓存、HTTP缓存、后台缓存(比如使用 Service Worker、PWA等技术)
  • 移除代码中无用的部分:比如使用 Tree-shaking、代码分割、移除用不上的依赖项等
  • 对资源进行合理的拆分(css、JavaScript脚本、图像/音频/视频等),减少请求资源的体积
  • 对资源进行压缩,减少传输数据的大小
  • 使用 HTTP2、HTTP3,提升资源请求速度
  • 对请求进行优化,比如对多个请求进行合并,减少通信次数;对请求进行域名拆分,提升并发请求数量

在请求资源返回后,浏览器会进行解析和加载,这个过程会影响页面的可见时间,通过对首屏加载的优化,可有效地提升用户体验

2. 首屏加载优化

将页面内容尽快展示给用户,减少页面白屏时间。
因此,主要包括 首屏加载耗时优化 以及 使用页面过渡效果
性能和渲染耗时优化

  • 对页面进行分片/分屏加载,将页面可见/可交互时间提前
  • 优化资源加载的顺序和粒度,仅加载需要的资源,通过异步加载方式加载剩余资源;
  • 使用差异化服务,比如读写分离,对于不同场景按需加载所需要的模块
  • 使用服务端直出渲染,减少页面二次请求和渲染耗时
  • 使用秒看技术,通过预览的方式(比如图片)提前将页面内容提供给用户
  • 配合客户端进行资源预请求和预加载,比如使用预热 Web 容器
  • 配合客户端将资源和数据进行离线,可用于下一次页面的快速渲染

使用页面过渡效果

  • 使用骨架屏进行预渲染
  • 使用过渡动画让用户感知到页面正在顺利加载

除了首屏渲染以外,用户在浏览器页面过程中,也会触发页面的二次运算和渲染,此时需要渲染过程的优化

3. 渲染过程优化

主要用于减少用户的操作等待时间,避免出现卡顿的情况

  • 使用资源预加载,在空闲时间,提前将用户可能需要用到的资源进行获取并加载
  • 减少 DOM 数量、减少/合并DOM操作,减少浏览器渲染过程中的计算耗时
  • 通过合理使用浏览器 GPU 合成,提升浏览器的渲染效率
  • 使用离屏渲染,在页面不可见的地方提前进行渲染(比如 canvas 渲染)
  • 通过将页面渲染帧率保持在 60 FPS 左右,提升页面交互和渲染的流畅度

除此之外,渲染过程同样可以使用页面过渡动画的方式(比如加载中),给予用户及时的反馈,来提升用户的体验
对于运算逻辑复杂、计算量大的业务逻辑,我们还需要进行计算/逻辑运行的提速

4. 计算/逻辑运行的提速

  • 通过将JavaScript大任务进行拆解 + 并行计算的方式,有效降低整体计算耗时,比如使用 Web Worker
  • 通过使用运行效率更高的方式,减少计算耗时,比如 Webassembly
  • 通过计算过程提前,减少计算等待时长,比如 AOT技术
  • 通过使用更优的算法或是存储结构,提升计算效率
  • 通过将计算结果缓存的方式,减少减少运行次数

在前端性能优化实践中,网络请求优化和首屏加载优化方案使用频率最高,因为不管项目规模如何、各个模块和逻辑是否复杂,这两个方向的耗时优化方案都是比较通用的。

空间角度优化:降低资源占用

大多数情况下优化效果会比耗时优化局限

  • 合理使用缓存,不滥用用户的缓存资源(比如浏览器缓存、IndexDB),及时进行缓存清理;
  • 通过使用数据结构享元的方式,减少对象的创建,从而减少内存占用;
  • 避免存在内存泄漏,比如尽量避免全局变量的使用、及时解除引用等;
  • 避免复杂/异常的递归调用,导致调用栈的溢出。

    如何在项目中进行性能优化

  1. 确定优化的目标和预期;
  2. 确定技术方案
  3. 对工作内容进行排期,并按计划执行
  4. 优化完成后,结合目标和预期,对优化效果进行复盘

性能数据:

  • 网络资源请求时间
  • Time To Start Render(TTSR):浏览器开始渲染的时间。
  • Dom Ready:页面解析完成的时间
  • Time To Interact(TTI)):页面可交互时间
  • Total Blocking Time (TBT):总阻塞时间,代表页面处于不可交互状态的耗时
  • First Input Delay(FID):从用户首次交互,到浏览器响应的时间。

性能指标

指标分为三类:文档加载相关、内容呈现相关、交互响应性相关

文档加载相关

Time to First Byte( TTFB )

浏览器从请求页面开始接收到第一字节的时间,这个时间包括 DNS查找,TCP连接,SSL 连接

DomContentLoaded (DCL)

DomContentLoaded 事件触发时间。DomContentLoaded 事件触发时间(HTML文档被完全加载和解析完),无需等待样式表、图像、子框架加载完成

Load (L)

onload事件触发的时间。onload事件触发时间(页面所有资源都加载完成,包括图片、CSS)。

内容呈现相关

像 Load 或 DOMContentLoaded 这样的度量并不能反映用户的视觉体验,因为它们的时间点不一定与用户在屏幕上看到内容的时间点对应。

First Paint (FP)

由 Web 性能工作组在 W3C 标准 Paint Timing 中提出。
从开始加载 到 浏览器首次绘制像素到屏幕上的时间。但此变化可能是简单的背景色更新或不引人注意的内容,它并不表示页面内容完整性,可能会报告没有任何可见的内容被绘制的时间。
这是开发人员关心页面加载的第一个关键时刻——当浏览器开始呈现页面时

First Contentful Paint (FCP)

浏览器 首次绘制来自DOM内容的时间。内容必须是文本、图片(包含背景图)、非白色的canvas或SVG,也包括带有正在加载的web字体的文本。
这是用户第一次开始看到页面内容。
另外,字体加载是影响 FCP 的一个重要因素,字体通常是需要一段时间才能加载的大文件,有些浏览器在加载字体之前会隐藏文本。为了确保在 webfont 加载期间文本保持可见,可以临时显示系统字体。
font-display: swap; 告诉浏览器使用该字体的文本应立即使用系统字体显示。一旦自定义字体就绪,将替换掉系统字体。

First Meaningful Paint (FMP)

页面的 主要内容绘制到屏幕上的时间。这是一个更好的衡量用户感知加载体验的指标,但是仍然不理想。
采用 页面布局数量最大且web字体已经加载的时间作为主要内容绘制的近似时间。

Largest Contentful Paint(LCP)

可视区域最大的内容元素呈现到屏幕上的时间,用以估算页面的主要内容对用户可见时间。
测量:new PerformanceObserver

First Screen Paint (FSP)

页面从开始加载到首屏内容全部绘制完成的时间,用户可以看到首屏的全部内容。

Speed Index (SI)

表示页面可视区域内容填充速度的指标。可以用可视区域的内容显示的平均时间来衡量。
这个平均时间可以用来比较首屏内容完整呈现给用户的性能体验,但它计算的不是首屏内容完整呈现这一时刻,不能算是一个用时间来度量的指标。

交互响应性相关

Time to Interactive(TTI)

表示网页第一次 完全到达可交互状态的时间,浏览器已经可以持续性的响应用户的输入。
完全到达可交互时间点是最后一个长任务完成的时间,并且在随后的5秒内网络和主线程是空闲的。

长任务是需要 50 毫秒以上才能完成的任

测量:tti-polyfill

First CPU Idle (FCI)

网页第一次可以响应用户输入的时间

First Input Delay

从用户第一次与页面交互到浏览器实际能够响应该交互的时间。
输入延迟是因为浏览器的主线程在忙于做其他事情,所以不能响应用户。发生这种情况的一个常见原因是浏览器正忙于解析和执行应用程序加载的大型JavaScript文件。
第一次输入延迟通常发生在 FCP 和 TTI 之间。因为这时候页面已经有内容但是还不能够可靠地交互。
测量: EventTime

Frames Per Second (FPS)

帧率是视频设备产生图像地速率,用每秒可以重新绘制的帧数表示。
重新绘制可能需要重新计算样式、布局和绘制。如果每帧绘制到屏幕的时间在16.7ms以上,每秒绘制的帧数就会小于60帧。人眼就能感受到页面的卡顿。所以60fps是页面流畅的目标。
如何降低重新绘制的时间?
尽量避免触发render树的重排流程比如css设置opacity
实现动画时,使用性能成本低的css而不是JavaScript设置。
测量:requestAnimationFrame

核心指标

用户体验核心指标 定义 衡量指标
白屏时间 页面开始有内容的时间,在没有内容之前是白屏 FP 或 FCP
首屏时间 可视区域内容已完全呈现的时间 FSP
可交互时间 用户第一次可以与页面交互的时间 FCI
可流畅交互时间 用户第一次可以持续与页面交互的时间 TTI

性能优化

性能优化总结:减少请求次数,减小资源大小,提高响应和加载速度,优化资源加载实际,优化加载方式。

构建优化

  1. gzip 压缩
    gzip 可以达到 70% 的压缩率
  2. 去除 console.log
  3. 去除 SourceMap
  4. CDN 减少打包体积
  5. 预渲染( prerender-spa-plugin ) (路由模式必须是 history)

    网络资源优化

  6. Service Worker
    Service Worker 是运行在浏览器后台进程的一段 JS,它可以做很多事情,比如拦截客户端请求,向客户端发送消息,向服务端发送请求等。最重要的是离线资源缓存
    ServiceWorker 拥有对缓存流程丰富灵活的控制能力,当页面请求到 ServiceWorker 时,ServiceWorker 同时请求缓存和网络,把缓存的内容直接给用户,而后覆盖缓存,可以用这个替代 http缓存策略

  7. HTTP 缓存
    HTTP 缓存分为两类:强缓存(本地缓存)和协商缓存( 304 缓存)
    普通刷新会跳过强缓存,启用协商缓存。
    本地缓存( 200 )是非常快速的一种方式,只要资源还在缓存有效期内,浏览器就会直接在本地读取,不会请求服务端
    协商缓存( 304 ):浏览器和服务端经过协商后,再决定是否使用本地缓存,如果服务器通知浏览器可以使用本地缓存,就会返回 304 状态,并且协商过程很简单,只会发送头信息,不会发送响应体。
    缓存位置:Memory Cache ( 内存缓存 ) 和 Disk Cache ( 硬盘缓存 )
    内存缓存:读取快,持续时间短,容量小
    硬盘缓存:读取慢,持续时间场,容量大
    缓存优先级:Service Worker > Memory Cache > Disk Cache > Push Cache
  8. 使用 HTTP 2.0
  9. 资源预加载
    对当前页面需要的资源使用 preload 预加载,对其他页面需要的资源使用 prefetch 预加载
    preload :页面加载过程中,在浏览器主体渲染之前加载

    1. <!-- 对sty1e.cs5和 index.js进行pre1oad预加载 -->
    2. <link rel="preload" href="style.css" as="style">
    3. <link rel="preload" href="index.js" as="script">


    prefetch: 页面加载完成后,使用空闲时间提前加载
    dns-prefetch:页面加载完成后,使用空闲时间提前加载
    异步无阻塞加载 js

    • defer: 延迟执行,在浏览器看来的效果是放在了body后面一样,按规范应该是在 DOMContentLoaded 事件前
    • async:异步执行,异步下载完成后就会执行。不确保执行顺序,一定在 onload 之前,但不确定在DOMContentLoaded 前还是后。
  10. webp:新的图片格式,体积只有 JPEG 的 2/3 。(浏览器兼容有问题)

    感知性能优化

  11. loading 加载

  12. 骨架屏

    加载优化

  13. 减少 HTTP 请求(比如尽量减少页面的请求输(同时请求不超过 4 个),合并 css 和 js,使用 CSS 精灵图等)

  14. 按需加载(比如懒加载,滚屏加载,media query 加载)
  15. 减少 cookie,cookie 会影响加载,静态资源域名不使用 cookie

    执行优化

  16. 避免 img、iframe等的src为空,空src会重新加载当前页面,影响速度和效率

  17. 避免重置图像大小,重置图像大小会引发图像的重绘
  18. 图像避免使用 DataURL,DataURL没有使用图像的压缩算法,文件会变大,并且要解码后再渲染,加载慢耗时长

    渲染优化

  19. 设置 viewport:会加速页面的加载,并且在移动端可以避免缩放导致的重绘重排

  20. 优化动画
    1. 尽量使用 CSS3 动画
    2. 合理使用 requestAnimationFrame 代替 setTimeout
    3. 适当使用 Canvas 动画
  21. 优化高频事件:scroll、touchMove 等事件可导致多次渲染
    1. 函数节流
    2. 防抖
    3. 使用 requestAnimationFrame监听帧变化,使得在正确的时间进行渲染
  22. GPU加速:合理使用某些HTML5标签和CSS3属性会触发GPU加速 (过度使用会导致耗电量增加)
    1. html标签:vedio、canvas、webgl
    2. css属性:opacity、transform、transition

      首屏优化

      排查并移除冗余依赖,静态资源

      压缩图片/使用webp格式的图片

      兼容低版本可以使用 标签兼容
      1. <picture>
      2. <source srcset="hehe.webp" type="image/webp">
      3. </picture>

      代码分割

      路由懒加载

      开启HTTP2

      gzip压缩传输

      Accept-EncodingContent-Encoding 对 采用何种编码格式传输正文 进行了协定
      也可以在构建时生成 gzip 文件

      托管至 OSS

      CDN加速

      CDN加速原理是把提供的域名作为源站,把源内容缓存到边缘节点。当客户端读取数据时,会从合适的节点(一般来说就近获取)获取缓存文件,以提高下载速度。

      感知性能优化

      loading动画

      骨架屏加载

      前端不同模式优缺点

      CSR

      优点:
  • 不依赖数据
  • FP时间最快
  • 客户端用户体验好
  • 内存数据共享

缺点:

  • SEO 不友好
  • FCP、FMP 慢

    预渲染

    优点:

  • 不依赖数据

  • FCP比 CSR 快
  • 客户端用户体验好
  • 内存数据共享

缺点:

  • SEO 不友好
  • FMP 慢

    SSR

    优点:

  • SEO 友好

  • 首屏性能高,FMP 比 CSR和预渲染快

缺点:

  • 客户端数据共享成本高
  • 模板维护成本高

    同构

    优点:

  • SEO 友好

  • 首屏性能高,FMP 比 CSR 和预渲染快
  • 客户端体验好
  • 内存数据共享
  • 客户端与服务端代码公用,开发效率高

缺点:

  • Node容易形成性能瓶颈

    骨架屏的实现

    在打包完成时在服务端利用 puppeteer 访问页面,获取页面html结构和样式,创建骨架屏的结构,将生成的html注入到最后生成的html中。