Vue 性能优化总结
一、内容方面
a)减少HTTP请求;
b)减少DOM元素数量;
c)使得Ajax可缓存;
二、针对CSS
a)多个 CSS 合并,尽量减少 HTTP 请求;
b)将 CSS 文件放在页面最上面;
c)移除空的 CSS 规则;
d)避免使用 CSS 表达式;
e)选择器优化嵌套,尽量避免层级过深;
f)充分利用CSS继承属性,减少代码量;
g)抽象提取公共样式,减少代码量;
h)属性值为0时,不加单位;
i)属性值为小于 1 的小数时,省略小数点前面的0;
j)CSS Sprite;(精灵图)
三、针对JavaScript
a)遵循严格模式:"use strict";
b)将JS脚本放在页面底部,加快渲染页面;
c)将JS脚本将脚本成组打包,减少请求;
d)尽量使用局部变量来保存全局变量;
e)尽量减少使用闭包;
f)使用window对象属性方法时,省略window;
g)尽量减少对象成员嵌套;
h)缓存DOM节点的访问;
i)通过避免使用 eval() 和 Function() 构造器;
j)给 setTimeout() 和 setInterval() 传递函数而不是字符串作为参数;
k)尽量使用直接量创建对象和数组;
l)最小化重绘(repaint)和回流(reflow)。
四、针对图片
a)不用图片,尽量用css3代替。
b)使用矢量图SVG替代位图。
d)按照HTTP协议设置合理的缓存。
e)使用字体图标webfont、CSS Sprites等。
f)用CSS或JavaScript实现预加载。
g)WebP图片格式能给前端带来的优化。
h)不要在HTML中使用缩放图片;
五、项目构建
①项目构建前:
1) 根据交互稿或视觉稿,合理划分pages,一般按照模块和子模块划分;
2) 使用路由懒加载,实现模块化与按需加载的效果,优化首屏性能;
3) 提取公共组件,尽量与业务隔离,如筛选条件等;
4) 提取公共方法,放在util.js中,如表单校验封装;
5) 提取在项目中出现较多的功能模块,如弹窗详情等;
6) 与后台开发人员定义接口协议,模块中能合并的尽量合并。
②项目构建中和完成后:
1) 开启eslint验证,看到错误和警告信息及时解决;
2) 打开浏览器调试窗口,查看Network中从发起网页页面请求Request后分析HTTP请求后得到的各个请求资源信息(包括状态、资源类型、大小、所用时间等),可以根据这个进行网络性能优化。如查看哪些http请求返回资源非常大或耗用时间较长,可针对处理;
3) 打开浏览器调试窗口,在Timeline中记录并分析在网站的生命周期内所发生的各类事件,以此可以提高网页的运行时间的性能;
4) 代码编写过程中不断思考是否可优化模块或做共性组件提取;
5) 及时代码审核,提高代码质量;
六、常见性能优化
1.总是用key配合v-for
在组件上总是必须用 key 配合 v-for,以便维护内部组件及其子树的状态。 同时,Key 不要为 index,尽量是一个唯一值,因为数据发生变化时,index 可能不变。
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
2.v-if 和 v-for不要同时作用于同一个元素
由于 v-for 和 v-if 放在同一个元素上使用会带来一些性能上的影响. 官方给出的建议是在计算属性上过滤之后再进行遍历。
Bad
<div
v-for="user in users"
v-if="user.isShow"
:key="user.id"
>
{{ user.name }}
</div>
<script>
export default {
data() {
return {
users: [
{
name: "小明",
isShow: true,
id: 1,
},
{
name: "小红",
isShow: false,
id: 2,
},
],
};
},
};
</script>
Good
<div v-for="user in activeUsers" :key="user.id">{{ user.name }}</div>
<script>
export default {
data() {
return {
users: [
{
name: "小明",
isShow: true,
id: 1,
},
{
name: "小红",
isShow: false,
id: 2,
},
],
};
},
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isShow;
});
},
},
};
</script>
3.组件的异步加载(按需加载组件/懒加载)
当页面很多,组件很多的时候,SPA页面在首次加载的时候,就会变的很慢。这是因为vue首次加载的时候把可能一开始看不见的组件也一次加载了,这个时候就需要对页面进行优化,就需要异步组件。
{
path: "/home",
name: "home",
component: require('@/views/home').default
// component: resolve => require(['@/views/home'], resolve)
},
{
path: "/home",
name: "home",
component: () => import('@/views/home')
},
4.使用 keep-alive 按需缓存路由页面
①使用include属性
<keep-alive include="要缓存的组件名称,要缓存的组件名称">
<router-view></router-view>
</keep-alive>
②通过路由传参实现 $route.meta.keepAlive
将 src/App.vue 中改为
<keep-alive >
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
在定义路由的文件中,设置 meta.keepAlive 参数
// 使用缓存的页面
{
path: '/XXX',
meta: {
keepAlive: true
},
name: '看板',
component: resolve => require(['@/views/kanban/index'], resolve)
},
③局部缓存
若同一页面中,有的组件使用缓存,有的组件不使用缓存,则使用keep-alive组件包裹需要使用缓存的组件即可
<!--列表组件使用缓存-->
<keep-alive >
<List />
</keep-alive>
<!--详情组件不使用缓存-->
<Detail />
④动态缓存
从不同页面进入相同页面,有时需使用缓存,有时不使用缓存,这时需使用到 组件内守卫:beforeRouteLeave(to, from, next)
从文章新增页面到文章列表页面——刷新文章列表
从文章详情页面到文章列表页面——不刷新文章列表
文章列表页的路由定义:
{
path: '/list',
name: '文章列表',
component: List.vue,
meta: {
keepAlive: true // 需要被缓存
}
}
文章新增页面组件中,设置beforeRouteLeave:
export default {
beforeRouteLeave(to, from, next) {
// 让文章列表不缓存,即刷新
to.meta.keepAlive = false;
next();
}
};
文章详情页面组件中,设置beforeRouteLeave:
export default {
beforeRouteLeave(to, from, next) {
// 让 文章列表页面 缓存,即不刷新
to.meta.keepAlive = true;
next();
}
};
注: keep-alive用来缓存组件,避免多次加载相应的组件,减少性能消耗,但keep-alive是一把双刃剑,确实需要缓存组件的时候才使用。
keep-alive参考链接:https://blog.csdn.net/weixin_41192489/article/details/112875654
5. scope中元素选择器尽量少用
在 scoped 样式中,类选择器比元素选择器更好,因为大量使用元素选择器是很慢的。
Bad
<template>
<button>X</button>
</template>
<style scoped>
button {
background-color: red;
}
</style>
Good
<template>
<button class="btn btn-close">X</button>
</template>
<style scoped>
.btn-close {
background-color: red;
}
</style>
6. ES6集合Set()可优化遍历速度
set集合是可用于查找该集合内是否存在某个元素,这种维度可有效的提高遍历效率。
使用场景:判断当一个数组中的元素是否在已选集合内(Set多用于数组去重等)
let arr = ["a", "b", "c"];
let set = new Set(arr);
console.log(set.has('a')); // true
console.log(set.has('d')); // false
Set详细使用:https://es6.ruanyifeng.com/#docs/set-map
7.数据冻结
如果有一个巨大的数组或 Object,并且确信数据不会修改,使用 Object.freeze() 可以让 Vue 不会给对象加上 getter/setter,性能会大幅提升。
Object.freeze() 方法冻结一个对象:
①一个被冻结的对象再也不能被修改;
②冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性
③不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。
④此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象
在组建中:
export default {
data: () => ({
users: {},
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
},
};
在 store 中:
const mutations = {
setUsers(state, users) {
state.users = Object.freeze(users);
// 也可以做修改
// state.users = Object.freeze([...state.users, user]);
},
};
深度冻结(遍历对象):
/**
* 深冻结
* @param {Object} target 要冻结的对象
* @return {Object} 冻结后的源对象
*/
export function deepFreeze(target) {
Object.freeze(target);
for (let propKey in target) {
let prop = target[propKey];
if (Object.prototype.hasOwnProperty.call(target, propKey) &&
prop !== null &&
(typeof prop === 'object' || typeof prop === "function") &&
!Object.isFrozen(prop)
) {
deepFreeze(prop)
}
}
return target;
}
8.销毁定时器
路由离开及时销毁定时器( 由于单页面应用路由地址变更页面不会刷新,定时器一直会存在,页面组件销毁时要清除 )
①beforeDestroy或destroyed中销毁
export default{
data(){
timer:null
}
}
//在方法(methods)或者页面初始化(mounted())的时候使用定时器
mounted() {
this.timer = setInterval(()=>{
//需要做的事情
},1000);
}
// beforeDestroy或destroyed中销毁定时器
beforeDestroy(){
clearInterval(this.timer);
this.timer = null;
}
注: 第一种方法确实是可行的,但存在问题:
1、vue实例中需要有这个定时器的实例,感觉有点多余;
2、 创建的定时器代码和销毁定时器的代码没有放在一起,通常很容易忘记去清理这个定时器,不容易维护
②使用this.$once()
直接在需要定时器的方法或者生命周期函数中声明并销毁
export default{
methods:{
fun1(){
const timer = setInterval(()=>{
//需要做的事情
console.log(11111);
},1000);
this.$once('hook:beforeDestroy',()=>{
clearInterval(timer);
timer = null;
})
}
}
}
9.清除事件监听
mounted() {
window.addEventListener('scroll', this.scrollhandle);
},
beforeDestroy() {
window.removeEventListener('scroll', this.scrollhandle);
},
window.addEventListener("scroll", this.scrollhandle);
this.$once("hook: destroyed", () => {
window.removeEventListener("scroll", this.scrollhandle);
});
methods: {
scrollhandle(event) {
...
},
},
如果是keep-alive组件,使用如下方式
activated() {
this.$refs.box.addEventListener('scroll', this.scrollEvent);
},
deactivated(){
this.$refs.box.removeEventListener('scroll',this.scrollEvent);
},
10.解绑事件
路由离开及时解绑事件:
created() {
this.$eventHub.$on('logged-in', this.getCurrentUser);
},
beforeDestroy() {
this.$eventHub.$off('logged-in');
},
methods: {
getCurrentUser(){
...
}
}
11.iframe的内存释放
相关资料称IE在iframe元素的回收方面存在着bug,需要手动将其释放以避免内存泄漏
释放方法:手动将iframe指向置空
<iframe
:src="url"
id="iframe"
width="100%"
/>
export default {
methods: {
clearIframe() {
let iframe = document.getElementById("iframe");
if (iframe) {
iframe.src = "about:blank";
try {
iframe.document.write("");
iframe.document.clear();
} catch (error) {
iframe.parentNode.removeChild(iframe);
}
}
},
},
beforeRouteLeave (to, from, next) {
this.clearIframe();
next();
}
}
ifame使用参考链接:https://www.jsopy.com/2020/01/12/vuecha9/#toc-heading-4
12.图片压缩
使用 image-webpack-loader 对图片进行压缩。
安装:
npm i -D image-webpack-loader
npm install gifsicle -D
在 vue.config.js 中 添加配置:
module.exports = {
configureWebpack: (config) => {
// 压缩图片
config.module.rules.push({
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: "image-webpack-loader",
},
],
});
},
};
13.打包优化
打包 vender 时不打包 vue、vuex、vue-router、axios 等,换用国内的bootcdn直接引入到根目录的 index.html 中。
vue.config.js:
const isProduction = process.env.NODE_ENV === 'production'; // 区分开发环境或生产环境
const cdn = {
css: [],
js: [
'https://cdn.bootcss.com/vue/2.6.10/vue.runtime.min.js',
'https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js',
'https://cdn.bootcss.com/vuex/3.1.2/vuex.min.js',
'https://cdn.bootcss.com/axios/0.19.0/axios.min.js'
],
};
module.exports = {
productionSourceMap: false, // 关闭线上源码(.map文件占用不小的空间,线上代码无需生成)
chainWebpack: config => {
// 生产环境配置
if (isProduction) {
// 生产环境注入cdn
config.plugin('html').tap(args => {
args[0].cdn = cdn;
return args;
})
}
},
configureWebpack: config => {
if (isProduction) {
// 用cdn方式引入
config.externals = {
vue: 'Vue',
vuex: 'Vuex',
'vue-router': 'VueRouter',
axios: 'axios'
}
}
}
}
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title>Vue打包CDN加速优化</title>
</head>
<body>
<noscript>
<strong>We're sorry but longdian-management doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
</body>
</html>
process.env.NODE_ENV参考链接:https://www.cnblogs.com/Rivend/p/12233207.html