掘金上有很多大佬分享过性能优化的经验贴,但有一些方式针对需要快速的迭代的h5不太适用,这里分享一些适用于h5开发(Vue)性能优化的方法
vue 自带了模块分析 可以打包命令后添加--report 就会生成一个report.html 直接运行即可看到各个模块占比
一.加载优化
1.路由懒加载:
使用路由懒加载,不做路由懒加载的话,用户打开首页,会一次性加载所有资源
css
{
path: '/loading',
name: 'loading',
component: () => import('@/views/loading/index.vue'),
},
原理:依托ES6动态加载模import(),调用 import() 之处,被作为分离的模块起点,意思是,被请求的模块和它引用的所有子模块,会分离到一个单独的 chunk 中,可以通过魔法注释设置模块名称,名称相同的将会被组合到相同的块中
2.组件懒加载:
通过import 异步引入对应组件资源,但这样可能导致请求增多
拆分原则:
1.该页面体积过大,导致加载缓慢,通过浏览器并行发起请求,提示下载速度
2.该组件不是一进入页面就展示,需要一定条件下才触发(比如弹框组件)
3.该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的 JS 文件大小(比如表格组件、图形组件等)
javascript
// vue 中实现异步加载组件
import { reactive, ref, onMounted, computed, watch,defineAsyncComponent } from 'vue'
const rule = defineAsyncComponent(() => (import('_c/rule')))
3.Tree shaking 优化
export default 导出的是一个对象,无法知道里面是否使用未导出的对象,但通过export 导出的函数就能利用tree-shaking 去除代码中没有使用的部分
4.骨架屏
插件:vue-skeleton-webpack-plugin 可以给不同页面设置骨架屏
白屏时间调试:谷歌的Performance 中FP/FCP(首屏渲染)
5.长列表虚拟滚动
如果使用长图去进行列表滚动,所需要的渲染时间会很长,滚动时还会造成页面卡顿,整体体验非常不好
虚拟滚动------指的是只渲染可视区域的列表项,非可见区域的不渲染,在滚动时动态更新可视区域,该方案在优化大量数据渲染时效果是很明显的,目前已有成熟的虚拟滚动插件
6.使用requestAnimationFrame 制作动画
(1)引擎层面
setTimeout/setInterval 属于 JS引擎
,requestAnimationFrame 属于 GUI引擎
JS引擎与GUI引擎
是互斥的,也就是说 GUI 引擎在渲染时会阻塞 JS 引擎的计算
(2)时间是否准确
requestAnimationFrame 刷新频率是固定且准确的,但 setTimeout/setInterval 是宏任务,根据事件轮询机制,其他任务会阻塞或延迟js任务的执行,会出现定时器不准的情况
7. 调整JS 的加载方式
js 常用的有6种加载方式,async、defer 是 script 标签的专属属性,对于网页中的其他资源,可以通过 link 的 preload、prefetch 属性来预加载
xml
1.正常加载:会阻塞 dom 渲染,浏览器必须等待 index.js 加载和执行完成后才能去做其它事情
<script src="index.js"></script>
2.异步加载:JS 不会阻塞 DOM 的渲染,async 加载是无顺序的,当它加载结束,JS 会立即执行
使用场景:使用场景:若该 JS 资源与 DOM 元素没有依赖关系,也不会产生其他资源所需要的数据时,可以使用async 模式,比如埋点统计
<script async src="index.js"></script>
3.defer 模式:defer 资源会在 DOMContentLoaded 执行之前,并且 defer 是有顺序的加载,如果有多个设置了 defer 的 script 标签存在,则会按照引入的前后顺序执行,即便是后面的 script 资源先返回
<script defer src="index.js"></script>
4.module 模式:在主流的现代浏览器中,script 标签的属性可以加上 type="module",浏览器会对其内部的 import 引用发起 HTTP 请求,获取模块内容。这时 script 的行为会像是 defer 一样,在后台下载,并且等待 DOM 解析
Vite 就是利用浏览器支持原生的 es module 模块,开发时跳过打包的过程,提升编译效率
<script type="module">import { a } from './a.js'</script>
5.preload:预加载,用于提前加载一些需要的依赖,这些资源会优先加载(如下图红框),vue2中 会自动给首页的所需资源添加预加载
5.1preload 加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞 onload 事件;
5.2preload 加载的 JS 脚本其加载和执行的过程是分离的,即 preload 会预加载相应的脚本代码,待到需要时自行调用;
<link rel="preload" as="script" href="index.js">
6.prefetch:利用浏览器的空闲时间,加载页面将来可能用到的资源的一种机制
prefetch 特点:
6.1 prefetch 加载的资源可以获取非当前页面所需要的资源,并且将其放入缓存至少5分钟(无论资源是否可以缓存)
6.2当页面跳转时,未完成的 prefetch 请求不会被中断
8.图片优化
1.图片动态裁剪:
利用阿里云/腾讯云cdn提供的图片动态裁剪功能,只需在图片的url地址上动态添加参数,就可以得到你所需要的尺寸大小,针对不同分辨率使用合适的图片可以有效减少图片尺寸。
ruby
// 具体形式 h高度 w宽度 e(0:按长边缩放,1:按短边缩放) c(0:)
<https://oss-console-img-demo-cn-hangzhou.oss-cn-hangzhou.aliyuncs.com/example.jpg@100h_100w_1e_1c>
2.图片懒加载:
对于一些图片量比较大的首页,用户打开页面后,只需要呈现出在屏幕可视区域内的图片,当用户滑动页面时,再去加载出现在屏幕内的图片,以优化图片的加载效果
图片懒加载实现原理:
由于浏览器会自动对页面中的 img 标签的 src 属性发送请求并下载图片,可以通过 html5 自定义属性 data-xxx 先暂存 src 的值,然后在图片出现在屏幕可视区域的时候,再将 data-xxx 的值重新赋值到 img 的 src 属性即可
二、图片渲染优化
css
1.CSS写在头部,JS写在尾部并异步
2.避免img、iframe等的src为空:空src会重新加载当前页面,影响速度和效率
3.尽量避免重置图像大小:多次重置图像大小会引发图像的多次重绘,影响性能
4.大图像尽量避免使用DataURL:DataURL图像没有使用图像的压缩算法,文件会变大,并且要解码后再渲染,加载慢耗时长
三、dom渲染优化
css
1.减少dom节点
2.优化动画
1.尽量使用CSS3动画
2.合理使用requestAnimationFrame动画代替setTimeout
3.GPU加速
使用某些HTML5标签和CSS3属性会触发GPU渲染
1.css 属性opacity、transform、transition
2.HTML标签 video、canvas、webgl
四.样式优化
arduino
1.避免在HTML中书写style
2.避免CSS表达式,CSS表达式的执行需跳出CSS树的渲染
3.正确使用display
display:inline后不应该再使用float、margin、padding、width和height
display:inline-block后不应该再使用float
display:block后不应该再使用vertical-align
display:table-*后不应该再使用float和margin
4.不声明过多的font-size
5.值为0时不需要任何单位
五.脚本优化
arduino
1.减少重绘和回流
避免不必要的DOM操作
避免使用document.write
减少drawImage
尽量改变class而不是style,使用classList代替className
用DOMFragment缓存批量化DOM操作
2.尽量避免操作Dom,可以先用
3.缓存.length的值:每次.length计算用一个变量保存值
六.构建优化
基于各类打包工具进行体积优化,
减少打包时间:缩减范围、
缓存副本、
定向搜索、
提前构建、
并行构建、
可视结构
减少打包体积:分割代码、
摇树优化、
动态垫片、
按需加载、
作用提升、
压缩资源
1.减少打包时间
javascript
1.配置include/exclude
减少检索的范围,一般是再各loader中进行配置
export default {
// ...
module: {
rules: [{
exclude: /node_modules/,
include: /src/,
test: /.js$/,
use: "babel-loader"
}]
}
};
2.缓存副本
配置cache缓存Loader对文件的编译副本,大部分Loader/Plugin都会提供一个可使用编译缓存的选项,通常包含cache字眼。以babel-loader
export default {
module: {
rules: [{
// ...
test: /.js$/,
use: [{
loader: "babel-loader",
options: { cacheDirectory: true }
}]
}]
},
};
3.定向搜索
配置resolve提高文件的搜索速度
export default {
// ...
resolve: {
alias: {
"#": '', // 根目录快捷方式
"@": '', // src目录快捷方式
swiper: "swiper/js/swiper.min.js"
},
// 模块导入快捷方式
extensions: [".js", ".ts", ".jsx", ".tsx", ".json", ".vue"] // import路径时文件可省略后缀名
}
};
4.并行构建
配置Thread将Loader单进程转换为多进程,若项目文件不算多就不要使用,开启多个线程也会存在性能开销。
5.可视结构,寻找体积过大的包
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
export default {
// ...
plugins: [
// ...
BundleAnalyzerPlugin()
]
};
2.减少打包体积
javascript
1.分割代码
webpack v4使用splitChunks替代CommonsChunksPlugin实现代码分割。
2.摇树
删除项目中未被引用代码,只对ESM模块有效,webpack中将模式设置为production就可以开启摇树
3.动态垫片
通过垫片服务根据UA返回当前浏览器代码垫片,好处是无需将繁重的代码垫片打包进去
4.按需加载
webpack中配合import()实现按需加载
// 魔术注解
const Login = () => import( /* webpackChunkName: "login" */ "../../views/login");
const Logon = () => import( /* webpackChunkName: "logon" */ "../../views/logon");
3.抽离第三方包
arduino
configureWebpack: config => {
// 再这里配置不需要进行打包的组件
config.externals = {
vant: 'vant',
axios: 'axios',
}
},
4.开启gzip压缩
一般正式项目上了cdn后 自动开启gzip压缩模式,所以一般无需配置,如果有需要的话,可以开启(需要nginx 也配置相关的) 默认nginx是可以直接转换压缩的,只不过直接有gz文件可以省去压缩的时间
javascript
// 开启gzip压缩
const CompressionPlugin = require('compression-webpack-plugin')
// vue.config.js
config.plugins.push(
new CompressionPlugin({
filename: '[path].gz[query]',
test: /.js$|.css$|.html$|.ttf$|.eot$|.woff$/, // 匹配文件名
threshold: 10240, // 对超过10k的数据压缩
deleteOriginalAssets: false, // 不删除源文件
})
)
5.分包 splitChunks
项目中的第三方库默认会被打包到一个文件名含vendors的bundle中,splitChunks可以抽离出常用的第三方包,不整合到主包中
yaml
// 分离第三方包
config.optimization = {
splitChunks: {
chunks: 'all',
// 入口和它的同步依赖最多能被拆分的数量,默认为3
maxInitialRequests:5,
cacheGroups: {
axios: {
name: 'axios',
test: /[\/]node_modules[\/]axios[\/]/,
priority: -10,
},
vant: {
name: 'vant',
test: /[\/]node_modules[\/]vant[\/]/,
priority: -10,
},
'vue-router': {
name: 'vue-router',
test: /[\/]node_modules[\/]vue-router[\/]/,
priority: -10,
},
pinia: {
name: 'pinia',
test: /[\/]node_modules[\/]pinia[\/]/,
priority: -10,
},
},
},
}