当前位置: 首页 > 科技观察

《前端优化》——避免在Vue中滥用this来读取data中的数据

时间:2023-03-12 07:27:39 科技观察

前言在Vue中,data选项是个好东西,把数据丢进去,你可以在Vue组件的任何地方通过this来读取数据中的数据数据。但要避免滥用它来读取数据中的数据。至于哪些地方可以避免滥用,滥用会造成什么后果,本专栏将一一揭晓。1.使用this读取data中数据的过程在vue源码中,data中的数据会添加getter函数和setter函数,转换成响应式的。getter函数代码如下:;if(Array.isArray(value)){dependArray(value);}}}returnvalue}使用this读取data中的数据时,会触发getter函数,其中varvalue=getter?getter.call(obj):val;获取到值后执行返回值,达到读取数据的目的。但是中间还有一段代码,其中会通过一系列复杂的逻辑操作来收集依赖。这里只需要知道Dep.target存在时就会收集依赖。这里可以得出一个结论。当Dep.target存在时,用这个读取data中的数据会收集依赖。如果滥用这个来读取数据中的数据,就会重复收集依赖,导致性能问题。2、Dep.target什么时候存在?Dep.target由依赖分配。依赖也称为Watcher(监听器)或订阅者。Vue中有三种依赖,其中两种很常见,分别是watch(监听器)和computed(计算属性)。还有一个隐藏的dependency-renderingWatcher,它是在模板第一次渲染时创建的。dep.target在创建依赖的时候被赋值,依赖的构造函数是Watcher创建的。lue=this.lazy?undefined:this.get();};Watcher.prototype.get=functionget(){pushTarget(this);try{value=this.getter.call(vm,vm);}catch(e){}functionWatcher(vm,expOrFn,cb,options,isRenderWatcher){//...if(typeofexpOrFn==='function'){this.getter=expOrFn;}else{this.getter=parsePath(expOrFn);}this.vareturnvalue};Dep.target=null;vartargetStack=[];functionpushTarget(target){targetStack.push(target);Dep.target=target;}在构造函数的最后会执行Watcher的实例方法get,实例方法中在pushTarget(this)中赋给Dep.target的值在get中执行。依赖是在Vue页面或组件第一次渲染时创建的,所以性能问题应该是第一次渲染慢导致的。3.在哪里滥用this读取data中的数据当Dep.target存在的时候执行滥用this读取data中的数据的代码会导致性能问题,所以需要搞清楚这些代码写在哪里才能执行,换句话说,找出在数据中滥用此读取数据的位置会导致性能问题。第二节介绍了Dep.target赋值后,会执行value=this.getter.call(vm,vm),其中this.getter是一个函数,所以如果用this读取data数据,它会收集依赖项,如果被滥用,会导致性能问题。this.getter是在创建依赖的时候赋值的,每个依赖的this.getter都不一样。让我们一一介绍。watch(listener)依赖的this.getter是parsePath函数,它的函数参数是要监听的对象。varbailRE=newRegExp(("[^"+(unicodeRegExp.source)+".$_\\d]"));functionparsePath(path){if(bailRE.test(path)){return}varsegments=path.split('.');returnfunction(obj){for(vari=0;i20){result+=this.a[key].num+this.b+this.c;}else{result+=this.a[key].num+this.e+this.f;}}returnresult;}}在计算属性d中,存在滥用this读取数据的情况。其中this.a是一个数组。此时Dep.target的值就是计算出的属性d的依赖关系。在循环this.a中,利用this获取中间的a,b,c,e,f的数据,从而可以对这些数据进行一系列复杂的逻辑操作来重复收集计算属性d这个依赖.这导致对计算属性d的值的访问变慢,从而产生性能问题。渲染Watcher的this.getter是一个函数,如下:updateComponent=function(){vm._update(vm._render(),hydrating);};其中vm._render()将模板template生成的渲染函数render转为虚拟DOM(VNode):vnode=render.call(vm._renderProxy,vm.$createElement);,举个例子说明一下渲染函数render是。例如模板模板:通过vue-loader渲染函数render会生成如下:(functionanonymous(){with(this){return_c('div',{attrs:{"class":"wrap"}},[_c('p',[_v(_s(a)),_c('span',[_v(_s(b))])])])}})with语句的作用是为一个或一组语句指定默认对象,例如with(this){a+b}等同于this.a+this.b,那么在template模板中使用{{a}}就等同于使用this读取data中的a数据。所以在模板template生成的渲染函数render中,也可能存在滥用this读取data中数据的场景。例如代码如下:在用v-for循环list数组的过程中,一直用这个读取data中间的arr和obj的数据,让这些数据进行一系列复杂的逻辑操作,反复收集这种依赖,导致初始渲染速度变慢,从而产生性能问题。4.如何避免滥用this读取data中的数据综上所述,在计算属性和模板中滥用this读取data中的数据会导致重复收集依赖,导致性能问题,那么如何避免这种情况。如何避免在计算属性中使用ES6对象解构赋值。计算属性的值是一个函数,其参数是Vue实例化的this对象。这可以在上面在计算属性中滥用它的示例中进行优化。优化前:computed:{d:function(){letresult=0;for(letkeyinthis.a){if(this.a[key].num>20){result+=this.a[key].num+this.b+this.c;}else{result+=this.a[key].num+this.e+this.f;}}returnresult;}}优化后:computed:{d({a,b,c,e,f}){letresult=0;for(letkeyina){if(a[key].num>20){result+=a[key].num+b+c;}else{result+=a[key].num+e+f;}}returnresult;}}上面使用解构赋值,将data数据中的a,b,c,e,f预先赋值给对应的变量a,b,c,e,f,然后在calculatedattributedata数据可以通过这些变量访问,不会触发data中对应数据的依赖集合。这样使用this只读取一次data中的数据,只触发一次依赖收集,避免了重复依赖收集带来的性能问题。如何避免在template模板中提前处理v-for循环中用到的数据,在v-for循环中不读取数组和对象类型的数据。这可以在上面模板中滥用它的示例中进行优化。假设list、arr、obj都是服务端返回的数据,arr、obj在任何模块渲染中都没有用到,那么可以这样优化。优化前:优化: