一直以前,前端性能优化都是一个面试必问的题目,面试官可能通过以下几个问题来进行提问:
- 从输入一个URL到最终页面渲染经历了什么?
- 项目中对webpack做了哪些配置上的优化?
- 项目中做过性能优化吗?
- 知道哪些性能优化的方式?
本篇文章就会对性能优化进行一个系统性的总结和实践。
开发构建阶段
提升打包速度
- 使用高版本的webpack
- 多进程打包
- thread-loader (官方推荐)
- paraller-webpack
- HappyPack (已经不在维护)
这里简单介绍下thread-loader的使用,安装好thread-loader后,在webpack rules中配置
{
test: /.js$/,
include: path.resolve('src'),
use: [
{
loader: 'thread-loader',
options: {
workers: 3
}
}
]
}
注:Vue cli默认是配置了parallel属性,在cpu内核大于1时,对Babel和TS使用thread-loader
减少打包体积
打包体积分析插件webpack-bundle-analyzer,根据生成的界面查看哪部分体积过大,再针对性优化
- 开启gzip
new CompressionWebpackPlugin({
test: /\.(js|css|json|txt|html|svg)(\?.*)?$/i,
deleteOriginalAssets: true, // 是否删除源文件,
algorithm: 'gzip',
// filename: '[path][base].gz',
// threshold: 0,
minRatio: 2
}),
- 压缩图片
image-webpack-loader
// vue.config.js中的配置如下
config.module
.rule('image')
.test(/\.(png|jpe?g|gif)(\?.*)?$/)
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
// 此处为ture的时候不会启用压缩处理,目的是为了开发模式下调试速度更快
disable: process.env.NODE_ENV === 'development'
})
.end()
- 通过IgnorePlugin忽略部分内容的打包
// 忽略moment 国际化内容的打包
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
- 合理划分chunks
通过SplitChunksPlugin进行公共脚本分离
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', // async initial all
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
// 提供公共脚本
commons:{
name:'commons',
chunks:'all',
minChunks:2
},
// 项目包生成vendor chunk
defaultVendors: {
test: /[\/]node_modules[\/]/,
priority: -10,
reuseExistingChunk: true,
},
},
},
},
};
- 基础库分离
将vue vue-router等资源通过CDN引入,不打入bundle中
利用缓存
- babel-loader开启缓存
options: {
cacheDirectory: true
}
cache-loader或者hard-source-webpack-plugin提升二次构建速度
缩小范围
- 通过exclude进行目录排除
- 通过path.resolve()
- 合理利用alias
资源加载阶段
- 图片格式的合理使用
-
PNG:无损压缩,支持透明,简单的图片尺寸较小,但是如果色彩丰富的图片存储为png,体积较大,不支持动画,一般用于logo icon等
-
GIF:支持动画,支持透明,但不支持半透明,边缘有杂边,文件小,只支持256中颜色。适合用于色彩简单的logo、icon、动图
-
JPG:有损压缩 体积小,不支持透明 适合色彩丰富的图片
-
webp:文件小,支持有损和无损压缩,支持动画、透明,但是兼容性不好
所以选择合适的图片格式很重要,小图片可以用base64
-
单页应用路由懒加载
-
组件按需加载
需要在babel里配置一下,比如:
plugins.push(['import', { 'libraryName': 'ant-design-vue', 'libraryDirectory': 'es', 'style': true // `style: true` 会加载 less 文件 }]) -
图片懒加载 可以使用vueLazyLoad
-
静态资源上CDN
CDN:内容分发网络,可以加快用户访问网络资源的速度和稳定性,减轻源服务器的访问压力,就近选择离用户近的CDN服务器进行资源的分配
-
升级到HTTP2.0
HTTP2.0是二进制帧协议,头部体积更小,解决了队头阻塞问题,支持双工,客户端和服务端都可以主动发送数据
-
preftch 和 preload
-
preload和prefetch的本质都是预加载,即先加载、后执行,加载与执行解耦。
-
preload和prefetch不会阻塞页面的onload。
-
preload用来声明当前页面的关键资源,强制浏览器尽快加载;而prefetch用来声明将来可能用到的资源,在浏览器空闲时进行加载。
-
不要滥用preload和prefetch,需要在合适的场景中使用。
-
开启缓存
使用强缓存和协商缓存
页面渲染相关优化
-
根据页面渲染过程进行合理的资源引入
页面渲染过程
a. 解析HTML生成DOM树 - 渲染引擎首先解析HTML文档,生成DOM树
b. 构建Render树 - 接下来不管是内联式,外联式还是嵌入式引入的CSS样式会被解析生成CSSOM树,根据DOM树与CSSOM树生成另外一棵用于渲染的树-渲染树(Render tree),
c. 布局Render树 - 然后对渲染树的每个节点进行布局处理,确定其在屏幕上的显示位置
d. 绘制Render树 - 最后遍历渲染树并用UI后端层将每一个节点绘制出来
现代浏览器总是并行加载资源,例如,当 HTML 解析器(HTML Parser)被脚本阻塞时,解析器虽然会停止构建DOM,但仍会识别该脚本后面的资源,并进行预加载。
同时,由于下面两点:
- CSS 被视为渲染阻塞资源(包括JS),这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕,才会进行下一阶段。
- JavaScript 被认为是解释器阻塞资源,HTML解析会被JS阻塞,它不仅可以读取和修改 DOM 属 性,还可以读取和修改 CSSOM 属性。
所以存在阻塞的 CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建。
另外:
- 当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。
- JavaScript 可以查询和修改 DOM 与 CSSOM。
- CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。
所以,script 标签的位置很重要。实际使用时,可以遵循下面两个原则:
-
CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。
-
JavaScript 应尽量少影响 DOM 的构建
-
加载CSS推荐用link减少用import
-
defer和async异步加载脚本
都是异步加载js资源, 但是区别是async加载完资源后会立即开始执行, 而defer会在整个document解 析完成后执行
-
减少回流和重绘
-
使用visibility:hidden替换display:none
-
使用transform代替top left
top是几何属性,操作top会改变节点位置从而引发回流,使用transform:translate3d(x,0,0)代替top,只会引发图层重绘,还会间接启动GPU加速 -
避免使用Table布局
-
避免规则层级过多
浏览器的
CSS解析器解析css文件时,对CSS规则是从右到左匹配查找,样式层级过多会影响回流重绘效率,建议保持CSS规则在3层左右 -
避免节点的属性值放在循环里当做变量
for (let i = 0; i < 10000; i++) {
const top = document.getElementById("css").style.top;
console.log(top);
}
const top = document.getElementById("css").style.top;
for (let i = 0; i < 10000; i++) {
console.log(top);
}
-
服务端渲染
CSR和SSR最大的区别在于前者的页面渲染是JS负责进行的,而后者是服务器端直接返回HTML让浏览器直接渲染,所以客户端渲染要经历js拉代码和执行的过程,就会出现首屏比较慢
动画相关
- 尽量使用 transition 和 animation来实现CSS动画,而不是JS实现动画(运行在主线程对动画的流畅度有影响)
- 动画尽量多用transfrom 和 opacity (无需重绘和回流,性能最好
- translateZ/translate3d 开启硬件加速
- JS动画使用requestAnimationFrame少用setInterval
代码相关
- 高频操作使用防抖和节流