一直想写篇前端性能相关的总结,个人觉得这块的内容会比较分散,面试的时候问起来,也不容易有一个清晰的框架,但是平时的习惯是想起来什么就写什么,所以攒了好久的内容只能躺在一堆笔记草稿里面;
---🚩🚩正文分割线🚩🚩---
按页面加载链路分类,从下面几个方面开始
-
首屏加载
-
代码优化
-
构建工具
首屏加载
这部分其实就是把从获取资源到页面呈现中可以优化的点提取出来
1. DNS预解析
不需要用户点击链接就在后台解析,在head中添加
html
<link rel="dns-prefetch" href="//example.com">
但是要注意会增加一定的网络请求和带宽消耗,非必要域名谨慎使用
2. 开启HTTP2
首先说下相对于HTTP1
的优势
- 多路复用 ,能够在单个
TCP
连接上同时传输多个请求和响应;HTTP1.1
有一个可选的Pipelining
技术,但它是按照顺序处理响应的,后发的请求可能被先发的请求阻塞,所以很多浏览器默认不开启。 - 首部压缩 ,使用HPACK算法对请求和响应头部进行压缩,减少了首部大小,节省了带宽。而在
HTTP/1.x
中,每次请求都需要发送完整的头部信息,很容易造成不必要的带宽浪费。 - 服务器推送 ,服务端可以在发送页面
HTML
,也就是客户端请求对应HTML
页面时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。 - 二进制分帧,使用二进制协议对数据进行分帧传输。二进制协议更高效,减少了解析数据的开销,并提高了传输速度。
在nginx中开启HTTP2
bash
# 修改nginx.conf中的配置
server {
listen 443 ssl http2;
server_name example.com;
# SSL证书和密钥路径
ssl_certificate /path/to/ssl/cert;
ssl_certificate_key /path/to/ssl/key;
# 其他配置项
}
3. 资源的预加载
这个其实是安排资源以更高的优先级进行下载和缓存,更详细的可以看看MDN文档
html
<link rel="preload" href="styles.css" as="style">
<link rel="preload" href="main.js" as="script" />
4. 动态创建加载脚本
不管是js
还是css
,在下载过程中其实都是会阻塞页面的,动态加载会在下载资源的同时,也不影响后续代码的执行
js
const script = document.createElement("script");
script.src = "myscript.js";
document.body.appendChild(script);
5. 同构渲染
其实就是服务端渲染(SSR)+ 客户端渲染(CSR),服务端渲染的升级版,像现在的Nuxt.js
或者Next.js
就可以实现;
引用《vue.js设计与实现》的对比来更直观的了解同构渲染的特点
CSR | SSR | 同构 | |
---|---|---|---|
SEO | 不友好 | 友好 | 友好 |
白屏问题 | 有 | 无 | 无 |
占用服务器资源 | 少 | 多 | 中 |
用户体验 | 好 | 差 | 好 |
6. 可见性优化
这部分主要是针对非可视区域进行延迟加载来减少首屏执行的逻辑
非可视区域
- 延迟接口请求 ,使用
setTimeout
或者then
函数来置后加载时机; - 图片懒加载 ,使用
IntersectionObserver
实现可视区域判断;
虚拟滚动
只加载上下及当前页的数据;可以通过滚动时分页或者vue-virtual-scroll-list
及react-virtualized
一类的插件实现;
7. 针对白屏/抖动
加载过程中无法避免的会有短暂白屏,
- 骨架屏,可以选择固定灰色块,或者计算页面元素宽高生成灰色快;
- loading ,加入比较有意思的
loading
动画; - 定义宽高,图片或者接口数据在渲染到页面之后,会撑开所在的元素,就造成页面抖动,设置好盒子或者图片的宽高或者设置个占位;
- 字体闪烁 ,加载字体且生效之前的闪烁,通过压缩字体减小资源体积,设置
font-display:block
来解决加载过程中的字体样式异常;
8. 静态资源
- 合理使用协商缓存和强缓存及本地存储
- 使用字体图标代替图片图标
- 使用webp
- 图片压缩
- 使用cdn
代码优化
这部分只是列出来可优化点,感兴趣可以去搜索相关实现,建议只有出现明确的性能问题存在时,才进行优化
1. JS
- 使用
script
的async
和defer
属性避免阻塞; - service worker,拦截网络请求,灵活的判断是否需要缓存资源;
- Web Worker,创建一个新的线程,在一个独立的js环境中执行逻辑,不会阻塞后续逻辑的执行,针对耗时的计算任务或者执行时间比较久的逻辑处理;
- 批量请求及事件任务切片;
- 节流和防抖;
- 事件委托;
- 及时销毁闭包及定时器;
- 缓存变量及dom属性;
- 变量作用域的合理声明;
2. CSS
- 回流属性放在一块集中修改;
- 避免选择器嵌套过深;
- 用
CSS
动画代替JS
动画; - 使用伪元素简化
html
结构,如:before代替div
; - 开启GPU加速,这个非必要不推荐开启;
- 减少
CSS
类名查找范围,浏览器解析CSS
遵循的是从右到左的查找规范,先找.b
再找.a
,将.wrap .a .b
改为wrap .b
;
3. Vue
v-show
和v-if
的合理使用,频繁更新显示状态使用v-show
;- 使用
keep-alive
和v-once
减少多余的更新渲染; - 通过
Object.freeze
移除双向绑定,减少不必要的数据监听; - 避免
template
中使用复杂的表达式;
4. React
- memo,减少子组件的重复渲染,简单组件不会有太大的效果,并且会加大内存消耗;
- useMemo,相当于Vue中的computed函数,所设置的依赖没有变化时,就会返回上一次的计算结果;
- useCallback,避免重复创建函数;
- 组件卸载清理 ,Class组件:
componentWillUnmount
,Function 组件:useEffect return
- 使用React Fragment,减少额外节点的渲染;
构建工具
目前基本上使用vite
,这里主要针对vite
优化,webpack
就简单带过
1. webpack
- 指定模块解析范围,设置解析文件类型范围
- webpack打包/构建缓存
hard-source-webpack-plugin
; - 资源的压缩,拆分、第三方包的提取合并(
config
配置optimization.splitChunks
);
2. 摇树优化
就是在保证代码运行结果不变的前提下,去除无用的代码;其实Rollup
会默认开启摇树优化,但需要是ES6 module
模块,第三方包尽管可能使用esm版本,本身体积会更小,而且能有更好的压缩效果
js
import { cloneDeep } from 'lodash'
// 改为
import { cloneDeep } from 'lodash-es'
const obj = cloneDeep({}) // 如果这行被注释,vite就不会再引入lodash包
删除线上的console
和debugger
,这个根据项目需求决定是否需要配置
js
{
esbuild: {
drop: ['console', 'debugger'],
}
}
3. gzip压缩
这个就是在客户端进行文件压缩,服务端直接调用
js
import viteCompression from 'vite-plugin-compression';
viteCompression({
verbose: true,
disable: false, // 不禁⽤压缩
deleteOriginFile: false, // 压缩后是否删除原⽂件
threshold: 10240, // 压缩前最⼩⽂件⼤⼩
algorithm: 'gzip', // 压缩算法
ext: '.gz', // ⽂件类型
}),
nginx配置静态gzip压缩,会直接读取文件夹中.gz文件
bash
# 修改nginx.conf中的配置
http {
gzip_static on;
}
Content-Encoding
为gzip
就表示设置成功了
4. 图片压缩
js
import viteImagemin from 'vite-plugin-imagemin'
viteImagemin({
gifsicle: { // gif图片压缩
optimizationLevel: 3, // 选择1到3之间的优化级别
interlaced: false, // 隔行扫描gif进行渐进式渲染
// colors: 2 // 将每个输出GIF中不同颜色的数量减少到num或更少。数字必须介于2和256之间。
},
optipng: { // png
optimizationLevel: 7, // 选择0到7之间的优化级别
},
mozjpeg: {// jpeg
quality: 20, // 压缩质量,范围从0(最差)到100(最佳)。
},
pngquant: {// png
quality: [0.8, 0.9], // Min和max是介于0(最差)到1(最佳)之间的数字,类似于JPEG。达到或超过最高质量所需的最少量的颜色。如果转换导致质量低于最低质量,图像将不会被保存。
speed: 4, // 压缩速度,1(强力)到11(最快)
},
svgo: { // svg压缩
plugins: [
{
name: 'removeViewBox',
},
{
name: 'removeEmptyAttrs',
active: false,
},
],
},
})
5. 依赖分析
基本上都是用这种方式来查找不必要的依赖引用来减小包体积
js
import { visualizer } from 'rollup-plugin-visualizer';
const command = process.env.npm_lifecycle_event
{
plugins: [
command === 'report' ?
visualizer({ open: true, brotliSize: true, filename: 'report.html' })
: null
]
}
最后
从整体架构方面还可以做下面几件事;
-
组件增加权重,针对单个组件,给组件添加权重值,针对权重大的组件优先展示;
-
按机型加载资源,根据当前系统版本、机型配置做不同的资源加载,动画交互降级;
-
微前端 拆分应用,剥离业务,减少业务之间的关联影响,使用
micro-app
或者qiankun
;
最后优化是有成本的,也需要根据场景决定是否进行优化