# 常见的性能优化方案
对于前端应用来说,网络耗时、页面加载耗时、脚本执行耗时、渲染耗时等耗时情况会影响用户的等待时长,而 CPU 占用、内存占用、本地缓存占用等则可能会导致页面卡顿甚至卡死。
因此,性能优化可以分别从 耗时和资源占用 两方面来解决,我个人也比较喜欢将其称为“时间”和“空间”两个维度。
# 时间角度优化:减少耗时
我们知道浏览器在页面加载过程中,会进行以下的步骤:
- 网络请求相关(发起 HTTP 请求从服务端获取页面资源,包括 HTML/CSS/JS/图片资源等)
- 浏览器解析 HTML 和渲染页面
- 加载 Javascript 代码时会暂停页面渲染(包括解析到外部资源,会发起 HTTP 请求获取并加载)
在浏览器的首次加载和渲染完成之后,不代表用户就可以马上交互和操作。根据业务代码加载过程,页面还会分别进入页面开始渲染、渲染完成、用户可交互等阶段。除此之外,页面交互过程中,会根据业务逻辑进行逻辑运算、页面更新。
题外话:为什么我们常常说要理解原理呢?性能优化便是个很好的例子,如果你不知道这个过程具体发生了什么,就很难找到地方下手去进行优化。
根据这个过程,我们可以从四个方面进行耗时优化:
- 网络请求优化。
- 首屏加载优化。
- 渲染过程优化。
- 计算/逻辑运行提速。
在前端性能优化实践中,网络请求优化和首屏加载优化方案使用频率最高,因为不管项目规模如何、各个模块和逻辑是否复杂,这两个方向的耗时优化方案都是比较通用的。相比之下,对于页面内容较多、交互逻辑/运算逻辑复杂的项目,才需要针对性地进行渲染过程优化和计算/逻辑运行提速。
一起来看看~
# 1. 网络请求优化
网络请求优化的目标在于减少网络资源的请求和加载耗时,如果考虑 HTTP 请求过程,显然我们可以从几个角度来进行优化:
- 请求链路:DNS 查询、部署 CDN 节点、缓存等。
- 数据大小:代码大小、图片资源等。
对于请求链路,核心的方案常常包括使用缓存,比如 DNS 缓存、CDN 缓存、HTTP 缓存、后台缓存等等,前端的话还可以考虑使用 Service Worker、PWA 等技术。使用缓存并非万能药,很多使用由于缓存的存在,我们在功能更新修复的时候还需要考虑缓存的情况。除此之外,还可以考虑使用 HTTP/2、HTTP/3 等提升资源请求速度,以及对多个请求进行合并,减少通信次数;对请求进行域名拆分,提升并发请求数量。
数据大小则主要考对请求资源进行合理的拆分(CSS、Javascript 脚本、图片/音频/视频等)和压缩,减少请求资源的体积,比如使用 Tree-shaking、代码分割、移除用不上的依赖项等。
在请求资源返回后,浏览器会进行解析和加载,这个过程会影响页面的可见时间,通过对首屏加载的优化,可有效地提升用户体验。
# 2. 首屏加载优化
首屏加载优化核心点在于两部分:
- 将页面内容尽快地展示给用户,减少页面白屏时间。
- 将用户可操作的时间尽量提前,避免用户无法操作的卡顿体验。
减少白屏时间除了我们常说的首屏加载耗时优化,还可以考虑使用一些过渡的动画,让用户感知到页面正在顺利加载,从而避免用户对于白屏页面或是静止页面产生烦躁和困惑。除了技术侧的优化,很多时候产品策略的调整,给用户带来的体验优化效果不低于技术手段优化,因此我们也需要重视。
整体的优化思路包括:尽可能提前页面可见,以及将用户可交互的时间提前。一般来说,我们需要尽可能地降低首屏需要的代码量和执行耗时,可以通过以下方式进行:
- 对页面的内容进行分片/分屏加载
- 仅加载需要的资源,通过异步或是懒加载的方式加载剩余资源
- 使用骨架屏进行预渲染
- 使用差异化服务,比如读写分离,对于不同场景按需加载所需要的模块
- 使用服务端直出渲染,减少页面二次请求和渲染的耗时
有些时候,我们的页面也需要在客户端进行展示,此时可充分利用客户端的优势:
- 配合客户端进行资源预请求和预加载,比如使用预热 Web 容器
- 配合客户端将资源和数据进行离线,可用于下一次页面的快速渲染
- 使用秒看技术,通过生成预览图片的方式提前将页面内容提供给用户
除了首屏渲染以外,用户在浏览器页面过程中,也会触发页面的二次运算和渲染,此时需要进行渲染过程的优化。
# 3. 渲染过程优化
渲染过程的优化要怎么定义呢?我们可以将其理解为首屏加载完成后,用户的操作交互触发的二次渲染。
主要思路是减少用户的操作等待时间,以及通过将页面渲染帧率保持在 60FPS 左右,提升页面交互和渲染的流畅度。包括但不限于以下方案:
- 使用资源预加载,提升空闲时间的资源利用率
- 减少/合并 DOM 操作,减少浏览器渲染过程中的计算耗时
- 使用离屏渲染,在页面不可见的地方提前进行渲染(比如 Canvas 离屏渲染)
- 通过合理使用浏览器 GPU 能力,提升浏览器渲染效率(比如使用 css transform 代替 Canvas 缩放绘制)
以上这些,是对常见的 Web 页面渲染优化方案。对于运算逻辑复杂、计算量较大的业务逻辑,我们还需要进行计算/逻辑运行的提速。
# 4. 计算/逻辑运行提速
计算/逻辑运行速度优化的主要思路是“拆大为小、多路并行”,方式包括但不限于:
- 通过将 Javscript 大任务进行拆解,结合异步任务的管理,避免出现长时间计算导致页面卡顿的情况
- 将耗时长且非关键逻辑的计算拆离,比如使用 Web Worker
- 通过使用运行效率更高的方式,减少计算耗时,比如使用 Webassembly
- 通过将计算过程提前,减少计算等待时长,比如使用 AOT 技术
- 通过使用更优的算法或是存储结构,提升计算效率,比如 VSCode 使用红黑树优化文本缓冲区的计算
- 通过将计算结果缓存的方式,减少运算次数
以上便是时间维度的性能优化思路,还有空间维度的资源优化情况。
# 空间角度优化:降低资源占用
提到性能优化,大多数我们都在针对页面加载耗时进行优化,对资源占用的优化会更少,因为资源占用常常会直接受到用户设备性能和适应场景的影响,大多数情况下优化效果会比耗时优化局限,因此这里也只能说一些大概的思路。
资源占用常见的优化方式包括:
- 合理使用缓存,不滥用用户的缓存资源(比如浏览器缓存、IndexDB),及时进行缓存清理
- 避免存在内存泄露,比如尽量避免全局变量的使用、及时解除引用等
- 避免复杂/异常的递归调用,导致调用栈的溢出
- 通过使用数据结构享元的方式,减少对象的创建,从而减少内存占用
说到底,我们在做性能优化的时候,其实很多情况下会依赖时间换空间、空间换时间等方式。性能优化没有银弹,只能根据自己项目的实际情况做出取舍,选择相对合适的一种方案去进行优化。
对于页面耗时和资源占用的性能优化分析,大部分情况都可以使用 Chrome 开发者工具进行针对性的分析和优化。