如何做前端性能优化?

大家好,我是前端西瓜哥,这次来看看如何做前端性能优化。

因为前端优化的方案非常多,本文不会太深入讲解,否则就篇幅太长了,主要还是让大家对大的脉络有一些认识。具体里面的小点以后我会另写文章讲解。

前端性能优化,主要分两个大方向:资源加载优化代码优化

资源加载优化

资源加载优化,大致又分为三种:

  1. 资源压缩

  2. 延迟加载

  3. 减少 HTTP 请求。在 HTTP/1 时代,请求效率不高,需要排队,会造成堵塞,减少 HTTP 请求还是非常有效的。但已广泛普及的 HTTP/2 后使用了多路复用,可以充分利用带宽,不用考虑这个。甚至说拆成更多的资源,有利于做更细粒度的缓存。

下面我们看看具体都有哪些方法。

使用 HTTP/2

HTTP/2 的主要改进就在于提高加载资源的速度。

它使用了头部压缩,通过特有的 HPACK 算法,让请求头做了极致的压缩。

此外使用了多路复用,让请求产生流的特性,可以同时发送多个请求,充分利用带宽;

懒加载

一些暂时不用到的资源先不急着加载,在用到的时候再加载,目的是减少请求。

懒加载有很多种,有图片懒加载,滚动到图片位置,才开始加载图片(知乎使用了这个,另外 Nextjs 框架的 Image 组件也支持懒加载)。

还有 JS 模块的懒加载,像 ES6 的动态 import,这个在 Webpack 有支持。

还有组件的懒加载:

1
{visible && <Model />}

开启 gzip

我们可以开启 Web Server 的 gzip 压缩,对资源进行内容压缩再传输。

此外还有压缩率更好的 br 可以选择。

前端性能优化:启用 gzip

图片使用 WebP

WebP 是 Google 推出的拥有极其优秀压缩比的图片格式。

我们可以判断客户端是否支持 WebP,如果支持则使用 WebP 来替代 jpg,减少图片大小。

空闲时加载之后可能需要访问的资源

加载当前页面资源的时候,调低某个暂时不用到的资源的下载优先级,在空闲的时候再加载,并将其缓存起来。

当用户从当前页面跳转到另一个页面,如果该页面用到了该资源,就可以直接使用缓存了。

1
<link rel="prefetch" href="lib/jquery.min.js" as="script">

关于资源加载管理的讲解,可以看我的这篇文章:

管理好资源的加载,你需要了解的 preload、prefetch、preconnect 以及 dns-prefetch

base64 内联

一些比较小的资源,比如一个不大的图标图片,可以考虑转换成 base64 格式,内嵌到 HTML 中。这样做的目的是减少 HTTP 请求数量。

下面是 webpack 的 url-loader 配置,将小于 8192 字节的图片转换为 base64。

1
2
3
4
5
6
7
8
9
10
11
{
  test/\.(png|jpg|gif)$/i,
  use: [
    {
      loader'url-loader',
      options: {
        limit8192,
      },
    },
  ],
},

雪碧图

英文原名为 CSS Sprites,指将多个小图片(比如图标)整合到一张大图片上,下载完后通过 CSS 的 background-position 属性框选需要用到的小图片上。

作用是减少 HTTP 请求数量,以及提前加载好一些图片资源,比如按钮图片的 hover 效果。

什么叫 “雪碧图”?

合理使用 HTTP 缓存

利用 HTTP 缓存策略,通过强缓存和协商缓存的配合,让一些资源能够不必再从服务端获取,而是直接复用本地缓存好的资源。

HTTP 缓存策略:强缓存和协商缓存

使用 SVG

多用矢量图,少用位图,减少图片大小。

普通位图图片要记录所有像素的色值,而 SVG 矢量图保存的其实是描述形状的文本信息,能减少很多体积。

CSS 放头 JS 放尾

CSS 放头,指的是放到 head 标签下的尾部,这样就能在 HTML 解析前,先加载 CSS 构造对应的 CSSOM,更早地和 DOM 进行合并渲染。

JS 放尾部,指的是放到 body 表情下的尾部。因为 JS 是会阻塞页面渲染并立即执行的,因为它可能会改变 DOM 结构。放到尾部,也就是整个 HTML 解析完之后,这样 JS 也能读取到完整的 DOM 树。

域名分片

域名分片是将需要的资源放到不同的域名下,提高 TCP 连接数。

因为 HTTP/1 的 TCP 连接下同时只能有一个 HTTP 请求,而网页打开时,往往至少有十几个的请求。为了让资源下载得更快些,浏览器会给一个域名建立 5~8 个 TCP 连接,通过并行的方式减少加载时间。

但有时候还是不够用,那我们就用多个域名呗,也就是域名分片,这样我们的 TCP 连接数就是 域名数 x 5,并发数量 UP!

使用缩略图

当我们通过列表的形式查看图片时,我们可以提供图片的缩略图,而不是提供大的原图。

用户点击预览的时候,才加载原图。

压缩文本类资源

这里的压缩指的是通过编译工具,将一些文本资源做内容的压缩。

比如 HTML、CSS、JS 中去掉多余的空格符,还有 JS 代码中一些名字很长的变量名缩减为一个字符、移除注释和没用到的变量等。

CDN

CDN,Content Delivery Network,内容分发网络。

CDN 其实就是将一些静态资源分发到位于不同位置的多个服务器上。

当用户请求资源时,首先会进行域名查询,得到域名对应的 IP 地址,在这里 CDN 会使用根据用户所在地的 IP 地址,提供一个最近的服务器 IP。

这样用户就能更快更稳定地获取资源,同时也减轻了源服务器的压力。

代码优化

节流和防抖

防抖和节流是比较常见的优化手段,常见于降低高频事件响应函数的响应。

节流是将原来的高频率的执行,调整为稳定的低频执行。

防抖是将高频率触发的请求降低为只执行最后一个函数。

事件委托

将有类似行为的大量子元素的事件响应函数,通过事件冒泡的方式,委托提升到父元素上,这样一个函数就可以替代多个函数,减少了内存。

JS 中的事件委托是什么?

使用缓存

在编程中,我们会经常使用哈希表,来缓存一些常用的数据,减少一些不必要的计算量。

这在算法题中还挺常用的,比如回溯要用备忘录来缓存一些结果。

长列表优化

长列表指的是列表项很多的列表,达到成千上万的规模。

如果要一次性将它们渲染出来,在渲染阶段容易遇到瓶颈,导致页面卡顿。常见的解决方案有两种:

  1. 使用时间分片:不一次性加载所有列表项,间隔一段时间渲染一批,直至全部渲染完;

  2. 使用虚拟列表:只渲染可视区域内的列表项

长列表优化:用 React 实现虚拟列表

Web Worker

Web Worker 相当于又新开了一个进程。单线程的 JS 进程可以和这个进程通信,传递数据和接受数据。

一些非常耗费算力的事情可以考虑放到 Web Worker,然后主进程就不会被阻塞。但 Web Worker 启动比较耗时,通常来说 Web Worker 在 90% 的场景都不需要用到。

注意内存泄漏问题

内存泄漏,指的是一些不需要的内存因为处理不当没能成功回收,导致占用内存越来越多,导致网页卡顿甚至崩溃。

导致内存泄漏的主要原因有:

  1. console 打印了太多的大对象;

  2. 忘记在必要的时候移除监听器,比如在组件卸载的时候;

  3. 意外声明全局变量。全局变量不会被销毁,会在程序运行的生命周期一直存在;

  4. 集合类数据类型存了一些不再使用的大的数据;

网页卡了?有可能是内存泄漏了

减少回流和重绘

回流:当渲染树中的一些元素、属性、尺寸等发生变化时,浏览器重新渲染部分或全部文档。

重绘:一些不改变元素物理属性的变化,比如改变背景色、字体大小,就不需要重新计算元素的位置,只要改一下样式就好。

回流导致的改变比较大,比较消耗性能,所以需要注意你的操作不要导致不必要的回流。比如往 DOM 树中加入一些元素,不要一个个加,而是要一次性将这些元素加进去。

React 相关优化

比如使用 React.memo 跳过一些组件的不必要渲染,进行状态的批量更新等。

另外,不要过早优化。

如何做 React 性能优化?

改用服务端渲染/预渲染

将一些数据在服务端就获取并渲染到 HTML 中,可以提高首屏加载速度。

服务端可以提前渲染好页面,而不是等待客户端加载完框架,然后请求数据再渲染出来。

结尾

本文简单列举了一些前端做性能优化的点,希望对你有一点帮助。

我是前端西瓜哥,欢迎关注我,学习更多前端知识。

本文首发于我的公众号:前端西瓜哥

作者

前端西瓜哥

发布于

2022-09-13

更新于

2023-10-14

许可协议