# 一、页面渲染
# 1. 减少页面重绘(repaint)和回流(reflow)
- 回流:可见节点的位置和几何信息发生改变触发重新布局
- 重绘:重新绘制节点,将渲染树的每个节点都转换为屏幕上的实际像素。元素样式发生改变, 但是宽高、位置等布局信息不会发生变化
- 利用 class 选择批量集中修改元素样式
- 复杂的动画元素要设置为
fixed
或absolute
,这样就不会在整个页面上引起回流了 - 不使用 table 布局(table 布局一旦触发回流就会导致 table 所有的其他元素回流)
- 需要创建多个 DOM 节点时,使用 DocumentFragment 一次性创建。
- css3 硬件加速(GPU 加速),transform、opacity、filter、will-change(慎用)这些属性可以开启、GPU 加速,不会引起回流和重绘。例如用 translate 代替 left、top 的移动
- 尽量给元素定义宽高,避免元素动态载入时,出现页面元素晃动或者位置变化导致回流
- 减少使用层级较深的选择器,以提高 css 渲染效率
- 在大量修改元素样式时,可以先用 display: none 将其隐藏,修改完再设置为 display: block,这样只会造成两次回流
# 2. 图片压缩、图片分割、雪碧图、iconfont
- 图片压缩:开发中可以使用 cli 或者 webpack、vite 等在打包时对图片进行压缩,当然也可以自己使用工具进行压缩,例如:tinyPNG (opens new window)
- 图片分割:如果遇到原画图这种大型图片,又不能进行压缩,我们可以将图片进行分割,最后使用 css 进行拼接
- 雪碧图:与分割相反,对于小型图片或者图标可以将其拼接到一张图片上,减少图片请求,使用 background-position 来移动背景图,从而显示出我们想要显示出来的部分。
- iconfont:对于矢量图片,可以上传阿里图标库或者使用其他图标库来减小图片的体积以及避免频繁请求,且不会失真
# 3. 字体压缩
- 问题:在特殊情况下可能会使用特殊字体包,来实现页面效果,但是很多完整的字体包很大,加载很慢导致字体刚开始不显示
- 解决:可以使用
font-spider
将会使用的文字提取出来,对于 SPA 项目可以使用spa-font-spider-webpack-plugin
# 4. 懒加载、预加载资源
- 懒加载:简言之就是只有当图片出现在浏览器的可视区域内时,才加载图片让图片显示出来(在此之前可以将所有图片元素的路径全都统一设置成一张 1*1px 的占位图)。
- 预加载:资源预加载包括预连接(preconnect)、预获取(preload)、预渲染(prerender)等。具体可以看前端预加载 (opens new window)
# 二、打包优化
# 1. webpack 优化 resolve.alias 配置
resolve.alias
配置通过别名来将原导入路径映射成一个新的导入路径。
可以起到两个作用:1.起别名;2.减少查找过程。
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
2
3
4
5
6
# 2. webpack 优化 resolve.extensions 配置
resolve.extensions
代表后缀尝试列表,它也会影响构建的性能,默认是:extensions: ['.js', '.json']
例如遇到 require('./data')这样的导入语句时,Webpack 会先去寻找./data.js 文件,如果该文件不存在就去寻找./data.json 文件,如果还是找不到就报错。
【所以后缀尝试列表要尽可能的小,不要把项目中不可能存在的情况写到后缀尝试列表中,频率出现最高的文件后缀要优先放在最前面,以做到尽快的退出寻找过程。】
resolve: {
extensions: ['.js', '.vue', '.json'],
}
2
3
# 3. webpack 缩小 loader 范围
loader 是很消耗性能的一个点,我们在配置 loader 的时候,可以使用 include 和 except 来缩小 loader 执行范围,从而优化性能。
{
test: /\.svg$/,
loader: 'svg-sprite-loader',
include: [resolve('src/icons')]
},
2
3
4
5
# webpack 代码分割
- 入口起点:使用
entry
手动分割代码 - 动态导入:通过模块动态导入(动态路由)分割代码
- 防止重复:使用 splitChunks 去重和分离 chunk
module.exports = {
//...
splitChunks: {
// 表示选择哪些 chunks 进行分割,可选值有:async,initial和all
chunks: "async",
// 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
minSize: 30000,
// 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。
minChunks: 1,
// 表示按需加载文件时,并行请求的最大数目。默认为5。
maxAsyncRequests: 5,
// 表示加载入口文件时,并行请求的最大数目。默认为3。
maxInitialRequests: 3,
// 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js
automaticNameDelimiter: "~",
// 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。
name: true,
// cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块,就分配到该组。模块可以被多个组引用,但最终会根据priority来决定打包到哪个组中。默认将所有来自 node_modules目录的模块打包至vendors组,将两个以上的chunk所共享的模块打包至default组。
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
//
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
以上配置,可以概括为如下 4 个条件(满足条件就会分割打包):
- 模块在代码中被复用或者来自 node_modules 文件夹
- 模块的体积大于等于 30kb(压缩之前)
- 当按需加载 chunks 时,并行请求的最大数量不能超过 5
- 页面初始加载时,并行请求的最大数量不能超过 3
vite 的配置如下:
build: {
rollupOptions: {
output: {
chunkFileNames: 'js/[name]-[hash].js', // 引入文件名的名称
entryFileNames: 'js/[name]-[hash].js', // 包的入口文件名称
assetFileNames: '[ext]/[name]-[hash].[ext]' // 资源文件像 字体,图片等
}
}
}
2
3
4
5
6
7
8
9
# 5. tree shaking
- tree shaking(摇树)。望文生义,它是用来清除我们项目中的一些无用代码,它依赖于 ES 中的模块语法得以实现。
- tree shaking 可以减小打包的体积,在 vite 和 webpack4 已上都是默认开启的。但是 tree shaking 只对 esModule 规范,因为 commonJs 在运行之前是不知道哪些模块需要哪些模块不需要的。
# 6. vite 关闭一些打包配置项
// webpack类似
build: {
terserOptions: {
compress: {
//生产环境时移除console
drop_console: true,
drop_debugger: true,
},
},
//关闭文件计算
reportCompressedSize: false,
//关闭生成map文件 可以达到缩小打包体积
sourcemap: false, // 这个生产环境一定要关闭,不然打包的产物会很大
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 三、总体优化
# 1. SSR 服务端渲染
SSR
(Server Side Rendering),即服务端渲染。它指的是渲染过程在服务端完成,最终的渲染结果 HTML 页面通过 HTTP 协议发送给客户端,又叫“同构“。
SSR 主要带来的好处就是 SEO 和首屏加载速度大大提高。 目前流行的 Vue/React 前端框架,都已经推出了 SSR 的解决方案:
对于 vite 的 ssr 可以参考vite+vue3 改造 SSR
# 2. 开启 gzip 压缩
gzip
对文件进行压缩,能大大提高首屏加载速度,对于纯文本文件我们可以至少压缩到原大小的 40%。
:::注意:图片最好不要进行 gzip 压缩!
因为一般情况下我们会在 loader 中对图片进行压缩或者自行进行压缩,此时再使用 gzip 压缩效果近似于无,但是由于对图片开启了 gzip 压缩反而会增加请求头等,增大请求的体积
:::
# 3. Brotli 算法压缩
Brotli
压缩算法 是 Google 2015 年推出的无损压缩算法。
- 启用 Brotli 压缩算法,对比 Gzip 压缩 CDN 流量再减少 20%。
- 针对常见的 Web 资源内容,Brotli 的性能相比 Gzip 提高了 17-25%;
- 当 Brotli 压缩级别为 1 时,压缩率比 Gzip 压缩等级为 9(最高)时还要高;
- 在处理不同 HTML 文档时,Brotli 依然能够提供非常高的压缩率。
- 服务端响应携带响应头 Content-Encoding: br:服务端响应的内容是经过 Brotli 压缩后的资源。
- 客户端请求携带请求头 Accept-Encoding: br:客户端希望获取对应资源时进行 Brotli 压缩。
- 兼容性:目前除了 IE 和 Opera Mini 之外,几乎所有的主流浏览器都已支持 Brotli 算法。
- Brotli 压缩支持的文件类型有 text/xml、text/plain、text/css、application/javascript、application/x-javascript、application/rss+xml、text/javascript、image/tiff、image/svg+xml、application/json、application/xml。
vite 项目开启Brotli
压缩:
- 使用
vite-plugin-compression
(opens new window)对平台进行 gzip 或者 brotli 压缩,nginx 对这两种压缩模式都支持,压缩后部署到 nginx 将极大提高网页加载速度。 - 修改 .env.production 文件,设置 VITE_COMPRESSION 全局变量即可,如下:
# 不开启压缩,默认
VITE_COMPRESSION = "none"
//以下配置压缩时不删除原始文件的配置
# 开启 gzip 压缩,
VITE_COMPRESSION = "gzip"
# 开启 brotli 压缩
VITE_COMPRESSION = "brotli"
# 同时开启 gzip 与 brotli 压缩
VITE_COMPRESSION = "both"
//以下配置压缩时删除原始文件的配置
# 开启 gzip 压缩
VITE_COMPRESSION = "gzip-clear"
# 开启 brotli 压缩
VITE_COMPRESSION = "brotli-clear"
# 同时开启 gzip 与 brotli 压缩
VITE_COMPRESSION = "both-clear"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 4. 缓存
缓存的原理就是更快读写的存储介质+减少 IO+减少 CPU 计算=性能优化。而性能优化的第一定律就是:优先考虑使用缓存。
缓存的主要手段有:浏览器缓存、CDN、反向代理、本地缓存、分布式缓存、数据库缓存。
对于浏览器缓存可以查看 (opens new window)
# 5. 组件按需引入
使用第三方组件库时,要按需引入,例如 import { Button } from 'vant';
# 6. 动态导入
- 使用
import()
动态引入第三方库或者组件
if (location.host !== "正式环境域名") {
import("@/utils/vconsole");
}
2
3
- 组件异步加载
// 1. import懒加载
() => import('@/pages/xxx.vue')
// 2. 使用require
resolve => require(['@/pages/xxx.vue'], resolve),
2
3
4
- 路由懒加载
//routes
{
path: '/index',
name: 'index',
component: () => import('@view/xxx.vue'),
//或者 component: require(['@/view/xxx.vue'], resolve),
meta: { title: '首页' }
}
2
3
4
5
6
7
8
# 7. CDN 内容分发
CDN 的全称是 Content Delivery Network,即内容分发网络。静态文件,音频,视频,js 资源,图片等都可以放 CDN 上。
CDN 的原理类似于京东,其实就是将资源放在遍布世界各地的服务器上,访问资源的时候,请求会重定向到 离用户最近的服务节点上。
# 8. 网络请求优化
- 在 H1.1 中并发多个请求需要建立多个 TCP 连接完成, 由于浏览器需要控制资源, 一般限制最多只能并发 6-8 个, chrome 是 6 个, 超过限制就会等待 name 我们可以将同一站点下的静态资源分布在不同域名下(域名发散)。例如:
主站域名www.a.com
访问图片的域名www.a-img.com
访问文件的域名www.a-link.com
2
3
- 可以使用 Http2,在 Http2 中:
- 同一个域名下所有的通信都在单个 TCP 连接内完成, 消除因多个 TCP 带来的延时和内存损耗
- 单个连接可以并行交错的请求和响应, 互相不会干扰
# 9. DNS 预解析
当你的网站第一次请求某个跨域域名时,需要先解析该域名(例如页面访问 cdn 资源,第一次访问需要先解析 cdn)。可以在请求的 Timing 上看到有一个 DNS Lookup 阶段,而在这个请求之后的其他该域名的请求都没有这项时间支出。具体查看
# 10. web worker
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。 合理实用 web worker 可以优化复杂计算任务。阮一峰的 WebWorker 使用教程 (opens new window)