十大秘诀!实现Vue.js极致性能优化
时间:2023-03-20 19:11:01
科技观察
Vue是一个用于构建用户界面的渐进式JavaScript框架。它具有体积小、运行效率更高、双向数据绑定、生态丰富、学习成本低等优点,因此Vue在移动端跨平台框架中也得到了广泛的应用。接下来,我将整理出10个实现Vue.js性能极致优化的技巧,供大家在实际应用中使用。Vue框架通过数据双向绑定和虚拟DOM技术帮助我们处理前端开发中DOM操作中最脏最累的部分。我们不再需要考虑如何操作DOM,如何最高效地操作DOM,但我们仍然需要去关注Vue在跨平台项目性能上的优化,让项目有更高效的性能和更好的用户体验经验。1.v-for遍历必须给item加一个key,避免在遍历和渲染列表数据的时候同时使用v-if,需要为每一个item设置唯一的key值,这样的内部机制Vue.js可以准确找到列表数据。状态更新时,将新状态值与旧状态值进行比较,快速定位差异。我们在使用中经常会使用index(即数组的下标)作为key,但实际上这并不是推荐的使用方式。例如:varlist=[{id:1,name:'test1',},{id:2,name:'test2',},{id:3,name:'test3',},]{{item.name}}
在最后一条数据之后再添加一条数据:varlist=[{id:1,name:'test1',},{id:2,name:'test2',},{id:3,name:'test3',},{id:4,name:'我最后加了一条数据',},]这时候直接复用前三块数据,新渲染最后一块数据。这时候使用索引作为key,就没有问题了。中间插入一条数据:varlist=[{id:1,name:'test1',},{id:4,name:'我是插队的那条数据',},{id:2,name:'test2',},{id:3,name:'test3',},]此时更新渲染数据,通过index定义的key对比数据前后的数据,发现:上一个datakey:0index:0name:test1key:0index:0name:test1key:1index:1name:test2key:1index:1name之后的数据:我是datakey:2index:2name:test3key:2index:2name:test2key:3index:3name:test3通过上面清晰的对比,发现除了第一个数据之前可以复用外,其他三个数据都需要重新渲染。不奇怪我只是插入了一条数据,为什么三条数据都要重新渲染?而我想要的只是呈现新添加的数据。最好的方式是使用数组中不会变化的item作为key值,对应item,即每条数据都有一个唯一的id来标识这条数据的唯一性;使用id作为键值,我们来对比一下中间插入一条数据时如何渲染。前面数据后面的datakey:1id:1index:0name:test1key:1id:1index:0name:test1key:2id:2index:1name:test2key:4id:4index:1name:我是datakey:3id:3index:2name:test3key:2id:2index:2name:test2key:3id:3index:3name:test3现在对比发现只有一条数据发生了变化,也就是id为4的那条数据,所以只需要渲染这条数据,other全部重用前。总结:总之,key的作用主要是高效更新虚拟DOM。另外,Vue在过渡具有相同标签名的元素时,也会使用key属性。目的也是为了让Vue区分它们,否则Vue只会替换其内部属性而不会触发过渡效果。v-for遍历避免同时使用v-if,v-for的优先级高于v-if,如果每次都需要遍历整个数组,会影响速度,尤其是需要渲染a的时候小部分,有必要将next替换为computed属性。2、长列表性能优化Vue会通过Object.defineProperty劫持数据,实现视图响应数据的变化。但是,有时候我们的组件是纯数据展示,没有任何变化,所以我们不需要Vue来劫持我们的数据,在大量数据展示的情况下,可以显着减少组件的初始化时间,那么如何防止Vue从劫持我们的数据?可以通过Object.freeze方法冻结对象。一旦对象被冻结,它就不能再被修改。exportdefault{data:()=>({users:{}}),asynccreated(){constusers=awaitaxios.get("/api/users");this.users=Object.freeze(users);}};三、Vue组件中的数据是一个函数而不是一个对象:'巴顿王',};},};取而代之的是:exportdefault{data:{//dataisanobjectname:'bartonwang',},};定义组件时,必须将数据声明为返回初始数据对象的函数,因为组件可能用于创建多个实例并在多个页面上重复使用。如果data是一个纯对象,所有的实例都会共享并引用同一个data数据对象,无论哪个组件实例修改了data,都会影响到所有的组件实例。如果数据是一个函数,则每次创建新实例时都会调用数据函数,从而返回原始数据对象的新副本。这样,每次复用一个组件,都会返回一条新的数据,类似于为每个组件实例创建一个私有数据空间,让每个组件实例都是独立的,互不影响,维护低耦合。4.Vuehook函数的hook事件hookEvent监听组件简化了代码使用:通过$on(eventName,eventHandler)监听一个事件。通过$once(eventName,eventHandler)监听一次事件。通过$off(eventName,eventHandler)停止监听事件。通常实现一个定时器的调用和销毁,我可能会这样实现:exportdefault{data(){timer:null//需要创建实例},mounted(){this.timer=setInterval(()=>{//具体执行内容console.log('1');},1000);}beforeDestory(){clearInterval(this.timer);this.timer=null;}}这个方法的问题是:Vue实例需要有这个定时器的实例,感觉有点多余。创建定时器的代码和销毁定时器的代码没有放在一起,不易维护,而且通常容易忘记清理定时器。使用$on('hook:')来监听beforeDestory生命周期可以避免这个问题,而且因为只需要监听一次,所以使用$once来注册监听。exportdefault{methods:{fn(){consttimer=setInterval(()=>{console.log('1');},1000);this.$once('hook:beforeDestory',()=>{//听一遍clearInterval(timer);timer=null;})}}}5.组件的懒加载在单页应用中,如果没有应用的懒加载,用webpack打包的文件会异常大,导致进入首页时需要加载太多内容,延迟时间过长,不利于用户体验。使用懒加载可以分页面,需要的时候加载页面,可以有效的分担首页的加载压力,减少首页的加载时间。.Vue.js2.0组件级懒加载解决方案:支持组件可见或即将可见时的懒加载,支持组件延迟加载,支持在加载真实组件之前显示骨架组件,提升用户体验,支持真实组件代码分包和异步加载安装:npminstall@xunlei/vue-lazy-component在组件中实现部分注册组件:import{componentasVueLazyComponent}from'@xunlei/vue-lazy-component'exportdefault{components:{'vue-lazy-component':VueLazyComponent}}需要懒加载的组件将其包裹在vue-lazy-component中,槽值骨架指的是懒加载时显示的加载状态组件。
6.非响应式数据初始化时,Vue会用getter和setter对数据进行改造。Vue的文档中在介绍数据绑定和响应时,特别标明通过了Object.freeze()方法的对象不能更新和响应。性能提升对比在一个基于Vue的大表benchmark中,可以看到渲染1000x10的表时开启Object.freeze()前后重新渲染的对比。开启优化前:开启优化后:在本例中,使用Object.freeze()比不使用快4倍。为什么不使用Object.freeze()的CPU开销,Object.freeze()的性能会更好?使用Object.freeze()的CPU开销:通过对比可以看出,使用Object.freeze()后,观察者的开销减少了。7.不要把所有的数据都放在data里。data中的数据会添加getters和setters,还会收集watchers,还是会占用内存的。我们可以在实例上定义不需要响应的数据。八、v-for元素绑定事件代理事件代理有两个主要作用:将事件处理器代理到父节点,减少内存占用。动态生成子节点时,事件处理程序可以自动绑定到父节点。每个跨度节点都绑定到一个点击事件并指向同一个事件处理程序,而不是使用事件代理:{{item}}
没有使用事件代理,每个span节点绑定一个点击事件,指向不同的事件处理器{{item}}
使用事件代理{{item}}}
可以看到使用事件代理时,监听数和内存占用都比前两者少同时对比三张图中的监听器数量,并没有发现Vue会自动充当事件代理,但一般在给v-for绑定事件时,节点会指向同一个事件处理器(即secondcase可以运行,但是eslint会warn),在一定程度上,比每次一个节点都绑定一个不同的eventhandler要好生成了,但是监听的数量还是不会变,所以还是用事件代理比较好。代码使用:meths(e){if(e.target.nodeName.toLowerCase()==='li'){console.log(e.target.innerHTML)console.log(e.target.dataset)}}九2.函数式组件函数式组件是无状态的,不能被实例化,没有任何生命周期和方法。创建功能组件也很简单,只需在模板中添加功能声明即可。一般适用于只依赖于外部数据变化的组件。由于重量轻,渲染性能也会得到提升。组件需要的一切都通过上下文参数传递。它是一个上下文对象,具体属性见文档。这里的props是一个包含所有绑定属性的对象。功能组件10.功能组件的通信痛点提供和注入组件:常见的父子组件通信方式是父组件绑定要传给子组件的数据,子组件通过props属性接收。一旦组件层级增加,使用这种方式逐级传值非常繁琐,代码可读性不高,不方便后期维护。Vue提供了provide和inject来帮助我们解决多层嵌套的嵌套通信问题。在provide中指定传递给子孙组件的数据,子孙组件通过inject注入祖父组件传过来的数据,可以轻松实现对父组件数据的跨级访问。provide:是一个对象,或者是一个返回对象的函数。它包含了要给后代的东西,也就是属性和属性值。注意:后代层的provide会覆盖祖父层provide中相同key的属性值。inject:一个字符串数组,或者一个对象。属性值可以是一个对象,包括from和default默认值,from是用于在可用注入内容中搜索的key(字符串或Symbol),表示爷爷multi-layerprovide提供了很多数据,而from属性指定取哪一个key;default指定默认值。从上面的例子可以看出,只要在父组件中调用,在父组件的有效生命周期内,所有的子组件都可以调用inject将父组件中的值注入。在使用场景中,肯定希望一旦父组件的数据发生变化,子孙组件也能得到父组件的更新数据。那么,如何实现父组件和子组件绑定数据的动态响应呢?------------------parent.vue------------------------provide(){return{//keyName:{name:this.name},//value是实现响应式的对象,即引用类型keyName:this.changeValue//也可以使用函数【注意,这里是使用函数作为值,而不是this.changeValue()]//keyName:如果'test'值是基本类型,则不能响应}},data(){return{name:'张三'}},methods:{changeValue(){this.name='改名-李四'}}------------grandson.vue--------------------inject:['keyName']create(){console.log(this.keyName)//改名-李四}