前端性能优化

性能优化

性能优化是老生常谈的问题,比如页面首屏渲染时间,丝滑程度等在现在的 web 应用中都显示十分重要,决定你的产品能否有更好的体验

度量

核心的 web 指标

Largest Contentful Paint(LCP): 最大内容绘制,测量加载性能,为了有良好的体验,LCP 在页面首次开始加载后的 2.5s 内出现

First Input Delay(FID): 首次输入延迟,测量交互性,100ms 内

Cumulative Layout Shift(CLS): 积累布局偏移,测量视觉稳定性,0.1s 内

FCP(First Contentful Paint): 首次内容绘制

TBT(总阻塞时间)、TTI(可交互时间) 等等等

可以通过浏览器的 performance API 获取,也可以调用一些包去获取上传

页面流畅度

页面流畅度一般使用流畅度(FPS)衡量,一般 50-60 会很流畅

Lighthouse & Chrome Devtools

Lighthouse 是谷歌开源的一款 Web 前端性能测试工具,它将针对此页面运行一连串的测试,然后生成一个有关页面性能的报告,可以直接在 chrome 中使用。Lighthouse Performance 项的总和得分由 5 个指标的性能按一定比例综合计算得到

Chrome Devtools Performance 也是可以分析运行时性能的良好工具

提升服务器的响应时间

页面中早建立连接

对服务器请求也会影响 LCP,尤其是当浏览器需要这些请求来在页面上显示关键内容的情况下。使用 rel=”preconnect”来告知浏览器您的页面打算尽快建立连接

1
2
3
4
5
6
<head>

<link rel="preconnect" href="https://example.com" />
// 对于不支持preconnect的浏览器,可以考虑将dns-prefetch做为后备。
<link rel="dns-prefetch" href="https://example.com" />
</head>

开启 HTTP/2 or HTTP/3

  • HTTP1.1 中 Chrome 中有个机制是默认运行同时建立 6 个 TCP 持久连接,使用持久连接时,虽然能公用一个 TCP 管道,但是在一个管道中同一时刻只能处理一个请求
  • HTTP/2 支持多路复用、头部压缩、服务端推送等特性为网站性能带来较大的提升且可以减少优化工作,然而 HTTP/2 同样使用的 TCP 连接,那么无法避免的有 TCP 的队头阻塞
  • HTTP/3 基于 UDP 的 QUIC 协议实现

缓存资源

浏览器缓存策略通过资源的 Response Header 来定义,如 cache-control、expires、Etag、last-modified 等 详细见浏览器的强制缓存和协商缓存

CDN

CDN(Content Delivery Network 内容分发网络):指一种通过互联网连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户

典型的 CDN 包括下面三个部分组成

  • 分发服务器系统: 最基本的工作单元就是 Cache 设备,cache(边缘 cache)负责直接响应最终用户的访问请求,把缓存在本地的内容快速地提供给用户
  • 负载均衡系统: 主要功能是负责对所有发起服务请求的用户进行访问调度,确定提供给用户的最终实际访问地址。两级调度体系分为全局负载均衡(GSLB)和本地负载均衡(SLB)。
  • 运营管理系统: 运营管理系统分为运营管理和网络管理子系统,负责处理业务层面的与外界系统交互所必须的收集、整理、交付工作,包含客户管理、产品管理、计费管理、统计分析等功能。
CDN 作用

CDN 一般会用来托管 Web 资源(包括文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。使用 CDN 来加速这些资源的访问。

  • 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快
  • 部分资源请求分配给了 CDN,减少了服务器的负载
CDN 原理

用户未使用 CDN 缓存资源的过程

  • 浏览器通过 DNS 对域名进行解析,依次得到此域名对应的 IP 地址
  • 浏览器根据得到的 IP 地址,向域名的服务主机发送数据请求
  • 服务器向浏览器返回响应数据

用户使用 CDN 缓存资源的过程

  • 对于点击的数据的 URL,经过本地 DNS 系统的解析,发现该 URL 对应的是一个 CDN 专用的 DNS 服务器,DNS 系统就会将域名解析权交给 CNAME 指向的 CDN 专用的 DNS 服务器
  • CDN 专用 DNS 服务器将 CDN 的全局负载均衡设备 IP 地址返回给用户
  • 用户向 CDN 的全局负载均衡设备发起数据请求
  • CDN 的全局负载均衡设备根据用户的 IP 地址,以及用户请求的内容 URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求
  • 区域负载均衡设备选择一台合适的缓存服务器来提供服务,将该缓存服务器的 IP 地址返回给全局负载均衡设备
  • 全局负载均衡设备把服务器的 IP 地址返回给用户
  • 用户向该缓存服务器发起请求,缓存服务器响应用户的请求,将用户所需内容发送至用户终端
CDN 使用场景
  • 使用第三方的 CDN 服务: 比如 jquery 等的
  • 使用 CDN 进行静态资源的缓存: 将自己网站的静态资源放在 CDN 上,比如 js、css、图片等。可以将整个项目放在 CDN 上,完成一键部署。
  • 直播传送

服务端渲染

优化手段只能在一定的限度内让我们的速度更快,但是如果想直出等效果需要改用不同的渲染方式去达到

传统的 SSR

传统的 SSR 也称为后端模板渲染,常见的就是 jsp 和 php,服务器在收到浏览器的页面请求后,使用模板引擎将页面模板与数据拼接成 HTML 返回

流程如下

  1. 浏览器输入 URL 地址–>浏览器向服务端请求页面
  2. 服务端收到请求–>路由匹配找到对应组件–>获取数据填充模板–>返回拼接数据引入 html
  3. 浏览器获取数据后开始渲染页面–>有 script 标签后再次向服务器请求 js –> 服务端收到请求返回 JS –> 浏览器收到 JS 后开始给各个页面注入交互

特点:

  • 首屏渲染快,客户端拿到的 HTML 是直接带数据的,首屏渲染直接有内容
  • 利于 SEO,服务端返回的 HTML 是带数据的
  • 避免首屏请求过多问题,也将传统的 3 次串性 http 请求合并为 1 次(请求 HTML、请求 JS、请求首屏数据)
  • 页面数据每次变动时,都需要重新请求完整的页面,性能差、服务端压力大、代码混乱、无法管理

CSR 渲染

服务端收到客户端请求后,只会返回前端打包生成的无页面内容的 HTML。需要客户端另外自行加载执行 JS,完成页面渲染。若需要页面首屏数据时,再去请求服务端,获取最新数据,更新视图

特点:

  • 前后端分离解耦
  • 可以进行页面局部刷新,无需请求完整页面,用户体验好
  • 节省服务端性能,不用支持渲染
  • 首屏渲染慢、不利于 SEO、首屏请求多

同构 SSR

采用一套代码、构建双端逻辑、最大限度的重用代码
既能在服务端渲染完成页面结构,又能在客户端渲染,绑定各客户端交互事件

同构 SSR 的出现主要基于

  • Nodejs 存在,提供了一套服务端的 JavaScript 的运行时
  • 虚拟 DOM 存在,引入虚拟 DOM 为 Nodejs 操作虚拟 DOM 提供了可能

React 中的同构主要包括了

  • 路由同构
  • 渲染同构
  • 数据同构获取

SSG、ISR、ESR

SSG(Static Site Generation):静态站点生成即静态渲染

无需使用 Web 服务器实时动态编译 HTML,而是在构建时(build time)为每个 URL 生成静态 HTML 文件,可以使用 CDN 加快页面访问

ISR(Incremental Site Rendering: 增量式渲染 = SSG + CSR)

ESR(Edge Side Rendering 边缘渲染)

ESR 渲染利用了 CDN 能力,CDN 节点相比于 Server,距离用户更近,有着更短的网络延时。ESR 会在 CDN 上缓存页面的静态部分,这样在用户访问页面时,可以快速返回给用户静态内容,同时在 CDN 节点上也发起动态部分内容请求,在动态内容获取之后,利用流的方式,继续返回给用户

阻塞渲染的 JS 和 CSS 资源

以 Webpack 打包工具举例去优化打包中的策略

削减 JS 和 CSS 的体积

引入 webpack-bundle-analyzer 插件对现有的分包进行分析,做产物分析,比如会遇到

  • 剔除重复的第三方库
  • 第三方库的筛选
  • 按需引入
  • 调试工具或者非线上依赖不打入包中

合理拆包、延迟、懒加载未使用的资源

使用内置的 splitChunks 配置去对 js 文件进行拆包,比如第三方库的拆分,方便浏览器缓存或者放入 CDN 中,引入 CDN 去实现
分包与业务相关,可以去动态加载一个 js 文件,比如动态路由之类的,等待使用了才回去加载 js 文件,让首页更加纯净,我们也会先加载用户最想看见的包,在会去加载一些没那么重要的包

  • 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。
  • 利用 CDN 加速:在构建过程中,将引⽤的静态资源路径修改为 CDN 上对应的路径。可以利⽤ webpack 对于 output 参数和各 loader 的 publicPath 参数来修改资源路径
  • Tree Sharking:将代码中永远不会⾛到的⽚段删除掉。可以通过在启动 webpack 时追加参数 –optimize-minimize 来实现
  • Code Splitting:将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
  • 提取第三方库:SplitChunksPlugin 插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

关键的 CSS

CSS 文件是渲染阻塞资源,这也就意味着,假如有个比较大的 css 文件,这个过程耗时就比较长。而我们初始页面实际上需要的 css 样式有多少
考虑将首屏内容 CSS 作为关键 CSS 提取出来,或者内联到 HTML 文档的 <head> 中(适合关键 CSS 体积较小的场景),从而无需发出额外的请求就能获取这些样式,或者作为最高优先级阻塞加载,剩余的 CSS 可以延迟加载或者根据分包懒加载。

加速缓慢资源加载

优化和压缩图片资源

通过图片压缩、格式自定义、分辨率自定义等方式去减小图片体积

预加载重要的资源

当有重要的资源比如字体、背景等加载的较晚时,采用
<link rel="preload">的方式实现预加载,以便需要时可以直接使用

压缩文本文件