万字前端效率大提速系列 🚀 :十一、前端性能优化专题

1,241 阅读8分钟

工作中我们需要解决各类卡顿、缓慢、白屏问题,优化产品用户体验。本篇会根据几种常见的场景,提供前端性能优化的思路和方案。

哪些环节可以性能优化?

大家知道,网页响应速度的快慢、用户交互是否丝滑是前端性能的主要考核点。在本系列第六篇中,有详细介绍用户访问网址到页面展示的过程。这里我们将这个过程分解,剖析其中潜在的优化点。

  1. 用户输入网址,DNS 会将网址中的域名解析为 IP 地址,以建立连接。在这个环节中,如何快速解析域名、建立物理距离尽可能短的连接,是性能优化的重点。

对于静态构建的前端项目,我们可以将文件部署到 CDN 服务器中,利用 CDN 就近服务、稳定传输的特点。快速建立稳定的文件传输通道。如果是服务端应用,同样可以参考 CDN 的机制,建立多个地区的服务器。

  1. 与服务器建立连接后,就开始传输页面资源,资源体积越小传输越快。我们要做的是打包出尽可能小的资源文件包括 js、css、图片等等

当前大部分项目都使用了构建工具如 webpack、vite。构建工具中的 按需加载、懒加载、tree shaking 等功能都能很好的帮助我们压缩 html、js、css 文件;对于图片资源我们可以考虑图片压缩、webp格式;我们还可以开启 gzip 来大大降低传输体积、开启缓存跳过传输过程。

  1. 通常前两步会占用比较多的时间,接收到资源文件后,浏览器会根据这些资源构建页面。大部分网页会需要再次请求服务端接口获取数据来渲染最终的页面。

此时我们应该了解浏览器对资源的解析过程,合理安排解析顺序来加快页面构建速度;还应该做好过渡动画、骨架屏,同时让服务端配合优化接口来提升页面渲染速度。

  1. 对前端同学来说还有一些特殊场景例如:小程序开发;APP、小程序中的 webview;同样我们可以通过减小打包体积、提前预加载页面来提升响应速度。

查看网页性能的方法

从上面的环节中可以看出,性能考核主要为两大步骤,一个是网络传输,一个是页面渲染和交互。

查看网络传输性能

网络传输主要通过 Chrome Dev Tool 中的 NetWork 选项卡查看。其中 overview 可以看到资源的加载时间线,即加载的开始时间和耗时;顶部工具栏可以勾选是否保留历史日志、是否禁用缓存、是否限制网速;中间的列表可以看到加载的文件名、响应状态码、大小、加载耗时;点击列表其中一条,可以看到这条请求的详细请求信息和返回内容。

image.png

通过加载顺序、大小、耗时,我们可以判断出页面资源加载的主要耗时点,例如 加载顺序不合理,资源没有并行加载; js 文件体积过大;图片文件体积过大;xhr 请求耗时过长等问题。将这些问题按优化性价比排列后再逐一解决。

查看页面渲染和交互性能的四种方法:

  1. window.performance 主要用来查看页面渲染过程的关键时间节点,文档:MDN Performance API

window.performance.png

  1. Chrome Dev Tool 中的 Performance 选项卡。在这个选项卡中,你可以录下一段时间的网页操作,查看这段时间中的 js 执行耗时、DOM 渲染耗时、界面绘制耗时、系统耗时。performance 官方文档(需翻墙)

performance.png

  1. Chrome Dev Tool 中的 lighthouse 选项卡。这里会给出 页面性能、PWA、无障碍、最佳实践、SEO 五项的分数,还会给出对应的建议。lighthouse 官方文档(需翻墙)

lighthouse.png

  1. 在 js 代码中自行打印执行时间,例如在执行前记录当前时间戳,在执行结束后减去执行前时间戳。用来记录某个函数、某段代码的执行时间。

具体如何优化

上面我们粗略了解了一些优化方案,这里我们说说具体该如何实现。我会给每个方案按优化效果和实现难度打分(打分比较主观,仅供参考)、按推荐程度排序,大家在实践中可以先使用性价比高的方案,快速起到优化效果。

1. 启用 gzip 压缩

优化效果 ★★★★★、实现难度 ★

gzip 后文件体积一般能压缩到原来的 1/3,能大大提升网页加载速度,在 response header 中观察有 content-encoding: gzip 请求头则开启了 gzip。配置以 nginx 为例,添加 gzip 配置即可:

server {
    #开启gzip
    gzip  on;  
    #低于1kb的资源不压缩 
    gzip_min_length 1k;
    #压缩级别【1-9】,越大压缩率越高,同时消耗cpu资源也越多,建议设置在4左右。 
    gzip_comp_level 3; 
    #需要压缩哪些响应类型的资源,多个空格隔开。
    gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;  
    #配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
    gzip_disable "MSIE [1-6]\.";  
    #是否添加“Vary: Accept-Encoding”响应头
    gzip_vary on; 
}

gzip 需要消耗一定的 CPU 资源,通常文本文件开启 gzip 收益是比较大的。图片等文件如果已经压缩过,可以根据压缩效果酌情开启。

2. 开启 HTTP 缓存

优化效果 ★★★★、实现难度 ★

HTTP 缓存对于二次加载的页面效果很好。如果打包出的 js、css 文件有哈希值,作为索引的 html 文件配置不缓存,而带哈希的文件可以设置长期缓存

  location / {
    ## 以 htm|html 结尾的文件加上不缓存的请求头
    if ($request_filename ~* .*\.(?:htm|html)$)  
    {
        add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
    }
    root   html; 
    index  index.html index.htm;
  }

3. 优化服务端接口响应速度

优化效果 ★★★★、实现难度 ★

这一块的实现难度其实是很高的,涉及 SQL 优化、负载均衡 等等,但因为通常不是我们前端同学开发,我们大可以把优化需求提给后端同学,帮助他们完成 KPI。

4. 启用 CDN 服务

优化效果 ★★★、实现难度 ★

如果你的服务器部署地点离常用用户不远,例如服务器在上海,用户主要是江浙沪,则提升不大。反之则应该使用 CDN 服务来部署页面资源以提高用户加载速度。

5. 图片使用 webp 格式

优化效果 ★★★★、实现难度 ★

首先我们检测浏览器是否支持 webp 图片,并注入全局变量 canUseWebp

let $img = new Image();
$img.onload = function () {
  window.canUseWebp = true;
};
$img.onerror = function () {
  window.canUseWebp = false;
};
$img.src =
  "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vlAAAA=";

然后准备好图片的 webp 地址和 png 地址,这两个地址的区别要有规律,方便编写公共方法

// 主要功能是拼好图片地址,怎么拼根据自身业务配置确定
getImgSrc(imgPath) {
    const host = 'cdn 域名'
    const imgType = window.canUseWebp ? 'webp' : 'png'
    return `//${host}/${imgType}/${imgPath}`
}

6. 合理安排 HTML 中的标签顺序

优化效果 ★★、实现难度 ★

由于浏览器解析 HTML 文件是从上到下的,我们可以根据重要程度安排标签顺序,script 标签可以酌情加上 asyncdefer 属性。 async 属性,可能阻塞解析也可能不阻塞,而defer 属性一定不阻塞。

7. 在 APP 中预加载 webview

优化效果 ★★★★★、实现难度 ★★★

这块实现涉及 APP 方的配合,需要 APP 在不影响用户正常使用的时机,预初始化 webview。这样在用户点进页面时,webview 已经提前准备好,并且预载了公共资源文件如 vendor.js。可以很大提升 webview 在 APP 中的体验。

具体实现在 安卓、IOS 中各有不同:

安卓Webview网页秒开策略探索

IOS WebView提高加载速度的几种优化方案

8. webpack 代码分离、tree shaking

优化效果 ★★★★、实现难度 ★★

在 webpack 的配置中,我们可以使用 entry 配置、SplitChunksPlugin 来将代码分离成多个 chunk(官方文档:代码分离);可以使用import('xxxx');来动态导入依赖

webpack 在构建中能够智能去除有引用但未使用到的无副作用代码,这个过程称为 tree shaking (官方文档),可以通过 package.json 的 sideEffects 属性标记无副作用的文件:

{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js"]
}

配置好代码分离后,想看看打包效果及分析,可以使用 Webpack Bundle Analyzer 等工具来分析:

image.png

9. 开发页面过渡效果、骨架屏

优化效果 ★★★★、实现难度 ★★★

在一些确实需要用户等待的场景,我们可以通过其他方式缓和等待的过程,提升用户体验。例如文章加载过程中,展示灰色骨架屏

image.png

还有页面、列表等待过程中加上 “加载中..” 样式,按钮加上点触反馈等等方法

虽然无法直接缩短用户的等待时间,但是在等待中起到了缓和效果,同样能够提升用户体验和产品质感。

结语

可以看到,常用的性能优化方案在各大技术平台都多有实践,难度都不高,大家只要根据自身的项目情况,锁定方案照搬即可。

学习资料

修言:前端性能优化原理与实践

阮一峰:网页性能管理详解