随着互联网的发展,愈来愈多的公司都在使用Vue
可是随着项目的愈来愈大,不免的会带来一系列的性能问题,笔者也为了这些问题而感到头疼,也一样的针对Vue
的性能优化进行学习,已便在项目之出把性能问题规避掉,避免没有必要的返工。javascript
为了方便之后可以快速的找到相关学习内容,在这里作一下记录,方便之后查看,同时也想把这些内容总结一下但愿可以帮助更多的小伙伴一块儿学习,一块儿成长。Fighting~html
这个时候可能会有不少小伙伴说,如今Vue3.0
都快发布了为何还要优化2.0
的项目?由于公司80%的项目全是Vue2.0
的项目,迁移的话成本过高,因此只能进行性能的优化调整。废话就很少赘述了,直接开始吧。前端
活用异步组件
Vue-cli
打包的时候会把全部依赖的文件打包成一个很大的一个js
文件中,当用户浏览网页的时候须要把整个js
文件拉取过来,这样会致使页面在初始化的时候,页面会出现长时间的白屏状况,这个问题确实是蛮棘手的。vue
设想一下若是在页面中有不少的功能点,每一个功能点又对应着不一样的功能弹窗或者表单,首先第一点页面中的功能不少,咱们不知道用户想要使用哪一个功能,须要弹出哪一个弹窗或表单,若是异步组件的状况,Vue-cli
在打包的时候会把异步组件单独打包成一个文件,当用户使用的才会去加载这个js
文件内容,这样不管是首屏的渲染起到了必定的优化的做用。java
看下官网对于异步组件的说明:webpack
在大型应用中,咱们可能须要将应用分割成小一些的代码块,而且只在须要的时候才从服务器加载一个模块。为了简化,Vue
容许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue
只有在这个组件须要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供将来重渲染。web
// 代码截取自Vue官网 Vue.component('async-webpack-example', () => import('./my-async-component') )
Vue
官方为了解决组件加载时的等待过长,提供了异步组件加载Loading
的异步组件:算法
// 代码截取自Vue官网 const AsyncComponent = () => ({ // 须要加载的组件 (应该是一个 `Promise` 对象) component: import('./MyComponent.vue'), // 异步组件加载时使用的组件 loading: LoadingComponent, // 加载失败时使用的组件 error: ErrorComponent, // 展现加载时组件的延时时间。默认值是 200 (毫秒) delay: 200, // 若是提供了超时时间且组件加载也超时了, // 则使用加载失败时使用的组件。默认值是:`Infinity` timeout: 3000 })
使用异步组件须要注意如下几点:后端
setTimeOut
也不是何尝不可的。v-if
,否则会报错,说test
组件未注册。v-if
是惰性的,只有当第一次值为true
时才会开始初始化。初始化减小DOM的渲染
仍然时上面所说的状况,页面中功能点不少,可是又不少弹窗什么的,其实对于这些弹窗最开始的时候考虑全部的弹窗只使用一个弹窗,为了节约页面初始化的渲染,可是在实际开发过程当中,虽然解决了一部分问题,仿佛在开发过程当中并非那么乐观,在弹窗内部出现了大量的v-if
和v-show
对于维护来讲太难了。数组
也有想过使用<component/>
组件,可是一个<component/>
所承受的压力可想而知不是一点半点的。可是这个问题任然是存在的须要获得解决。没有办法的状况下,最后使用了两个flag
去控制弹窗的显示与隐藏。
<template> <div> <el-dialog title="提示" v-if="isRenderDialog" :visible.sync="isShowDialog"></el-dialog> <el-button @click="onShowDialog">Render Dialog</el-button> </div> </template> <script> export default { data:() => ({ isRenderDialog:false, isShowDialog:false }), methods: { onShowDialog(){ !this.isRenderDialog && (this.isRenderDialog = true); this.$nextTick(() => { this.isShowDialog = true; }) } } } </script>
上述代码中使用两个flag
值控制Dialog
一个是控制Dialog
的渲染,一个控制Dialog
的显示,当用户首次进入页面的时候则dialog
元素不会被渲染,当用户点击按钮,对应的Dialog
才会被渲染出来,当Dialog
的DOM
渲染完成使用在使用显示Dialog
。
注:在$nextTick
中显示dialog
是为了保证dialog
的动画效果,若是不使用$nextTick
则dialog
就会很生硬的出现。
组件内部请求数据
你们在作业务的时候,可能会有这种状况,当点击按钮以后,须要获取到该条数据的详情渲染到弹窗或者侧滑中,这种状况必定不在少数啦。笔者在开始作这个的时候就是,在点击的时候直接去获取点击的元素的详情数据,当数据返回以后把数据放到data
中缓存,以后再传到组件中。
这样作不是不可行的,也是能够的,这样就会面临一个问题,第一点就是当弹窗中的渲染的元素过多的状况下,侧滑或者弹窗的动画效果会很卡,有的时候甚至是不动,瞬间就消失了。
最后通过反复的实验,把数据放到弹窗内部组件中去请求,保证弹窗或者侧滑出现的时候内置元素较少,当数据没有请求回来以前须要把弹框组件内的全部元素隐藏,使用loading
代替,当弹窗或者侧滑关闭的使用须要把显示的组件销毁掉,保证里面的数据所占用的内存被释放,这样对于总体优化仍是有一些帮助的。
tamplate少计算
因为业务状况的复杂程度,不免会某一个地方添加各类条件的渲染,例如:v-if="isHide && selectList.length && (isA || isB)"
,这里也只是举一个简单的栗子可能在实际的开发过程当中的状况远比这个要复杂的多,这种表达式看上去虽说是能够维护的,可是久而久之下去就会暴露问题,这样作是很不利于维护的。
对于这种状况能够适当的使用methods
或computed
封装成方法,其实这样作的好处是方柏霓咱们判断相同的表达式,若是其余的元素也有相似的需求能够直接使用这个方法。
v-for && v-bind:key
在使用v-for
循环过程当中,使用:key="item.id"
这样的代码对于代码的性能是很不友好的,由于当data
数据更新的时候,新的状态值会和旧的状态值作对比,Vue
在多diff
算法的时候可以更快的定位到虚拟DOM
的元素上。
其实说到这里就须要说明如下key
在vue
中到底起到一个什么样的做用,key
属性实际上是vue
的一个优化,上文也说了就是为了更精准高效的定位到虚拟DOM
,至关于使用key
给数组中某个预算绑定到了一块儿,若是那个key
对应的数据发生了变化,直接更新对应的DOM
就能够了。
对于简短的for
来讲能够直接使用index
做为key
可是,若是大型列表的话最好仍是不要使用index
做为key
了。举个栗子,例如数组删除了一个元素,那么这个元素后方元素的下标全都前移了一位,以前key
对应的数据和dom
就会乱了,除非从新匹配key
,那就容易产生错误。若是从新匹配key
,等于所有从新渲染一遍,违背了使用key
来优化更新dom
的初衷。可是若是对于Vue
玩的很透的同窗来讲能够能够忽略这一条。
Object.freeze
若是对Vue
有必定了解的小伙伴都知道Vue
是经过Object.defineProperty
对数据进行挟持,来最终实现视图响应数据的变化,可是在实际的开发过程当中,页面中有一部分可能不须要进行双向绑定,只是作单纯的渲染,数据一旦绑定以后不须要再作出任何改变的时候可使用Object.freeze
对数据作解绑。
先介绍如下Object.freeze
内置函数,用于对接对象,冻结后的对象不会在被修改,不能对这个对象进行添加新属性, 不能删除已有属性,不能修改该对象已有属性的可枚举性,可配置性,可写性.此外冻结一个对象后该对象的原型也不能进行修改。
当数据量大的时候,这可以很明显的减小组件初始化的时间,这里有一个须要注意的点就是一旦被冻结的对象就不再能被修改了。可是这里有一个问题须要注意的是,嗒嗒嗒,敲黑板!敲黑板!敲黑板!
虽然Object.freeze
在必定程度上可以帮助咱们提高一部分的数据性能,可是在使用的时候仍然须要谨慎使用。避免形成数据没法响应的问题。若是使用Object.freeze
这个属性再次给其对象属性赋值时,则会抛出错误不能分配给对象的只读属性*
。
用这种方法去提高性能若是数据量小的状况是没法感受出来的。只有数据量大的时候,才会感受到数据的明显变化。
渲染前处理
在渲染数据的时候,后端所返回的数据和UI
设计图中所须要的数据格式不一致,好比:列表中须要展现一个时间,可是后端返回的是一个时间戳,那么前端就须要对这部分数据进行处理。通常来讲处理这种状况有一些办法,使用函数,使用filter
,还有就是在渲染以前把数据处理好。
笔者这里比较建议在渲染以前把全部的数据处理好,为何?数据渲染以后完成以后才会去执行里面的函数或者是过滤器,这样会给页面渲染形成很明显的额外的负担。若是对Vue3.0
了解的同窗能够知道,在Vue3.0
中已经把filter
这个功能已经去掉了,推荐使用computed
来实现相同相同的效果。
猜想内容:可能尤大大也发现了filter
给页面渲染带来的额外的负担,并无对页面的性能提高起到很大的做用。
functional
不是不少函数组件都须要方法,Vue
中为了表示一个模板应该被编译成一个功能组件,在模板中 添加了functional
属性。若是项目中的所使用的组件不是有状态的组件,那么就可使用functional
属性把这个组件抓换成功能组件。
功能组件(不要与Vue的render函数混淆)是一个不包含状态和实例的组件。功能组件是一个没有状态或实例的组件。因为功能组件没有状态,由于不须要为Vue
的数据响应之类的东西作初始化动做。功能组件仍然会像出入的props
同样对数据更新作出响应,可是功能组件的自身,因为它不维护本身的状态,同时也所以没法知道本身的数据是否已经发生了改变。在大型项目中使用功能组件之后,在对于DOM
渲染有重大的改进。
因为功能组件没有状态,所以不须要为Vue的反应系统之类的东西进行额外的初始化。功能组件仍然会像传入的新道具那样对更改作出反应,可是在组件自己内,因为它不维护本身的状态,所以没法知道什么时候数据已更改。
在许多状况下,功能组件可能不合适。毕竟,使用JavaScript
框架的目的是构建更具反应性的应用程序。在Vue
中,若是没有适当的反应系统,则没法执行此操做。
假设咱们的组件接受一个prop.user
,该对象是带有firstName
和的对象lastName
,而且咱们想要呈现一个显示用户全名的模板。在功能<template>
组件中,咱们能够经过在组件定义上提供一个方法,而后使用$optionsVue
提供的属性来访问咱们的特殊方法来作到这一点:
<template functional> <div>{{ $options.userFullName(props.user) }}</div> </template> <script> export default { props: { user: Object }, userFullName(user) { return `${user.firstName} ${user.lastName}` } } </script>
子组件中处理业务
页面中也会有不少的列表,列表中也会有各类各样的复杂的状况,这个时候能够把一些比较繁重的业务处理存放到其子组件中。
代码对比:
<template> <div :style="{ opacity: number / 300 }"> <div>{{ heavy() }}</div> </div> </template> <script> export default { props: ['number'], methods: { heavy () { const n = 100000 let result = 0 for (let i = 0; i < n; i++) { result += Math.sqrt(Math.cos(Math.sin(42))) } return result }, }, } </script>
优化后:
<template> <div :style="{ opacity: number / 300 }"> <ChildComp/> </div> </template> <script> export default { props: ['number'], components: { ChildComp: { methods: { heavy () { /* 长任务在子组件里。 */ } }, render (h) { return h('div', this.heavy()) } } } } </script>
当组件随着props:number
的变化,组件patch
从新渲染的时候,heavy
长任务也会从新执行。可是若是能将没有与父组件相互依赖的元素,拆成一个组件,在父组件须要从新渲染的时候,由于与父组件没有依赖子组件并不会跟着从新渲染,响应的性能也能获得提高。
局部做用域
开发过程当中会常用到一些计算属性或者Util
函数,若是咱们在循环过程当中,不断的使用this.***
去调用一个计算属性的时候,每次调用这个值计算属性都会计算一次,然而这个值确是一个固定不变的值,就形成了很大的性能的浪费。
若是当咱们使用这些属性的时候,最好的方式是把对应的值取出来,而后再去使用。
<template> <div :style="{ opacity: start / 300 }">{{ result }}</div> </template> <script> export default { props: ['start'], computed: { base () { return 42 }, result ({ base, start }) { let result = start for (let i = 0; i < 1000; i++) { result += Math.sqrt(Math.cos(Math.sin(base))) + base * base + base + base * 2 + base * 3 } return result }, }, } </script>
总结
以上是我经过调查资料以及我的项目中的一些小经验得出的对于Vue
性能优化的一些方案,可能文章中一些看法存在一些问题,欢迎你们在评论区指出,你们一块儿学习,一块儿进步。