Vue 性能优化总结

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`nextTick`是Vue框架提供的一个API,用于在DOM更新之后执行一个回调函数,这个回调函数可以访问到更新后的DOM。 Vue的数据驱动模式,当数据发生变化时,Vue会异步执行一系列的更新操作,而不是立即更新DOM。因此,当我们需要在DOM更新后执行某些操作时,我们需要使用`nextTick`。 具体来说,`nextTick`的作用是将回调函数推入一个队列中,在当前代码执行完成后,Vue会遍历这个队列,依次执行队列中的回调函数。这样做的好处是,我们可以在DOM更新完后执行回调函数,从而保证操作能够访问到最新的DOM。 下面我们来看一个具体的例子: ``` <template> <div>{{ message }}</div> </template> <script> export default { data() { return { message: 'Hello Vue!' }; }, methods: { handleClick() { this.message = 'Hello World!'; this.$nextTick(() => { console.log('DOM updated!'); }); } } }; </script> ``` 在上面的代码中,我们定义了一个`message`变量,然后在`handleClick`中修改了它的值,接着使用`$nextTick`在DOM更新后执行了一个回调函数,输出了`DOM updated!`。 需要注意的是,`nextTick`并不是立即执行回调函数,它会等到所有同步任务执行完毕后才会执行。这是因为Vue会将异步更新操作放入微任务队列中,而微任务会在所有同步任务执行完毕后立即执行。 总结一下,`nextTick`的作用是在DOM更新后执行回调函数,从而保证操作能够访问到最新的DOM。它会将回调函数推入一个队列中,在当前代码执行完成后,Vue会遍历这个队列,依次执行队列中的回调函数。需要注意的是,`nextTick`并不是立即执行回调函数,它会等到所有同步任务执行完毕后才会执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值