Avoid abusing this to read data in data in Vue

  • 2021-11-01 01:46:26
  • OfStack

Preface to the table of contents
1. The process of reading data from data with this
2. When does Dep. target exist
3. Where to misuse this to read data in data
4. How to avoid abusing this to read data in data

Preface

In Vue, the data option is a good thing to throw the data inside, and the data in data can be read through this anywhere in an Vue component. But avoid abusing this to read data in data, where to avoid abuse, and what the consequences will be, this column will reveal 11.

1. The process of reading data from data with this

In the Vue source code will be data data add getter function and setter function, will be turned into a responsive. The code for the getter function is as follows:


function reactiveGetter() {
 var value = getter ? getter.call(obj) : val;
 if (Dep.target) {
  dep.depend();
  if (childOb) {
   childOb.dep.depend();
   if (Array.isArray(value)) {
    dependArray(value);
   }
  }
 }
 return value
}

When reading data from data with this, the getter function is triggered, in which the getter function is passed var value = getter ? getter.call(obj) : val; Execute after getting the value return value To achieve the purpose of reading data.

It can be concluded here that when Dep. target exists, using this to read data in data will collect dependencies. If this is misused to read data from data, dependencies will be collected repeatedly, resulting in performance problems.

2. When does Dep. target exist

Dep. target is assigned by dependency. Dependencies are also called Watcher (listener) or subscriber. There are three kinds of dependencies in Vue, two of which are common, namely watch (listener) and computed (computed property). There is also a hidden dependency-rendering Watcher, created during the first rendering of the template.

Dep. target is assigned when the dependency is created, and the dependency is created with the constructor Watcher.


function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
 //...
 if (typeof expOrFn === 'function') {
  this.getter = expOrFn;
 } else {
  this.getter = parsePath(expOrFn);
 }
 this.value = this.lazy ? undefined : this.get();
};
Watcher.prototype.get = function get() {
 pushTarget(this);
 try {
  value = this.getter.call(vm, vm);
 } catch (e) {
  
 }
 return value
};
Dep.target = null;
var targetStack = [];
function pushTarget(target) {
 targetStack.push(target);
 Dep.target = target;
}

The instance method is executed at the end of the constructor Watcher get In the instance method get Execute in pushTarget(this) That assigns a value to Dep. target in.

Dependencies are created when an Vue page or component is first rendered, so the performance problem should be that the first render is too slow.

3. Where to misuse this to read data in data

When Dep. target exists, executing these codes that abuse this to read data in data will cause performance problems, so it is necessary to find out where these codes are written before being executed. In other words, it is necessary to find out where abusing this to read data in data will cause performance problems.

Dep is described in Section 2. target is assigned to execute value = this.getter.call(vm, vm) , of which this.getter Is a function, so if this is used to read data data, it will collect dependencies, and if abused, it will cause performance problems.

this.getter Is assigned during the creation of dependencies, and the this.getter They are all different. Let's introduce 11 below.

watch (listener) depends on this.getter Yes return value0 Function whose function parameter is the object to listen to.

var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]"));
function parsePath(path) {
 if (bailRE.test(path)) {
  return
 }
 var segments = path.split('.');
 return function(obj) {
  for (var i = 0; i < segments.length; i++) {
   if (!obj) {
    return
   }
   obj = obj[segments[i]];
  }
  return obj
 }
}

In the code shown below a And a.b.c Pass in as a parameter return value0 The function returns a function assignment to the this.getter , execution this.getter.call(vm, vm) Will get this.a And this.a.b.c The value of. In this process, there will be no scenario of abusing this to read data in data.


watch:{
 a:function(newVal, oldVal){
 // Do something 
 }
}
vm.$watch('a.b.c', function (newVal, oldVal) {
 //  Do something 
})
computed (computed properties) depends on this.getter There are two kinds. If the value of the evaluated attribute is a function, then this.getter This is the function. If the value of the calculated property is an object, then this.getter This is the get property value of this object, and the get property value is also a function. In this function, there may be scenarios where this is abused to read data in data. For example, the code is as follows.

computed:{
 d:function(){
  let result = 0;
  for(let key in this.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;
   }
  }
  return result;
 }
}

There is an abuse of this to read data data in the computational attribute d. Among them this.a Is an array, and the value of Dep. target is the dependency of d, which is evaluated in the loop this.a this is used to obtain the data of a, b, c, e and f, and these data are subjected to a series of complex logical operations to repeatedly collect the dependency of calculating attribute d. Causes slow retrieval of the value of the computed attribute d, resulting in performance problems.

Rendering Watcher's this.getter Is a function as follows:

updateComponent = function() {
 vm._update(vm._render(), hydrating);
};

Among them vm._render() The rendering function render generated by template template is converted into virtual DOM(VNode):vnode = render.call(vm._renderProxy, vm.$createElement); Give an example to illustrate what the rendering function render under 1 is.

For example, the template template:


<template>
 <div class="wrap">
 <p>{{a}}<span>{{b}}</span></p>
 </div>
</template>

With vue-loader, the rendering function render is generated, as follows:


(function anonymous() {
 with(this) {
  return _c('div', {
   attrs: {
    "class": "wrap"
   }
  }, [_c('p', [_v(_s(a)), _c('span', [_v(_s(b))])])])
 }
})

Where the with statement is used to specify a default object for one or one group of statements, for example with(this){ a + b } Equivalence this.a + this.b Is used in the template template {{ a }} this is used to read a data in data. Therefore, in the rendering function render generated by template template, there may be a scene of abusing this to read the data in data. For example, the code looks like this:


<template>
 <div class="wrap">
 <div v-for=item in list>
  <div> {{ arr[item.index]['name'] }} </div>
  <div> {{ obj[item.id]['age'] }} </div>
 </div>
 </div>
</template>

In the process of using v-for to loop list array, this is used to read the data of arr and obj in data continuously, so that these data are collected repeatedly by a series of complex logical operations, which leads to slow rendering speed for the first time, resulting in performance problems.

4. How to avoid abusing this to read data in data

To sum up, abusing this to read data in data in computed attributes and template templates will lead to repeated collection of dependencies, which will cause performance problems. How to avoid this situation?

How to avoid in calculating attributes

It is avoided by deconstructing and assigning values with ES6 objects. The value of calculated attributes is a function, and its parameters are instantiated this objects of Vue, which can be optimized in the above examples of abusing this in calculated attributes.

Before optimization:


computed:{
 d:function(){
  let result = 0;
  for(let key in this.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;
   }
  }
  return result;
 }
}

After optimization:


function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
 //...
 if (typeof expOrFn === 'function') {
  this.getter = expOrFn;
 } else {
  this.getter = parsePath(expOrFn);
 }
 this.value = this.lazy ? undefined : this.get();
};
Watcher.prototype.get = function get() {
 pushTarget(this);
 try {
  value = this.getter.call(vm, vm);
 } catch (e) {
  
 }
 return value
};
Dep.target = null;
var targetStack = [];
function pushTarget(target) {
 targetStack.push(target);
 Dep.target = target;
}
0

Above, a, b, c, e and f in data data are assigned to corresponding variables a, b, c, e and f in advance by deconstruction assignment, and then data data can be accessed through these variables in calculation attributes without triggering dependence collection of corresponding data in data. In this way, only this reads the data in data once, and only triggers one dependency collection, thus avoiding the performance problems caused by repeated dependency collection.

How to Avoid in template Template

Handle the data used in the v-for loop in advance, and do not read the data of array and object type in the v-for loop. This can be optimized in the above example of abuse of this in the template template.

Assuming that list, arr and obj are all data returned from the server, and arr and obj are not used in any module rendering, they can be optimized in this way.

Before optimization:


function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
 //...
 if (typeof expOrFn === 'function') {
  this.getter = expOrFn;
 } else {
  this.getter = parsePath(expOrFn);
 }
 this.value = this.lazy ? undefined : this.get();
};
Watcher.prototype.get = function get() {
 pushTarget(this);
 try {
  value = this.getter.call(vm, vm);
 } catch (e) {
  
 }
 return value
};
Dep.target = null;
var targetStack = [];
function pushTarget(target) {
 targetStack.push(target);
 Dep.target = target;
}
1

After optimization:


function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
 //...
 if (typeof expOrFn === 'function') {
  this.getter = expOrFn;
 } else {
  this.getter = parsePath(expOrFn);
 }
 this.value = this.lazy ? undefined : this.get();
};
Watcher.prototype.get = function get() {
 pushTarget(this);
 try {
  value = this.getter.call(vm, vm);
 } catch (e) {
  
 }
 return value
};
Dep.target = null;
var targetStack = [];
function pushTarget(target) {
 targetStack.push(target);
 Dep.target = target;
}
2

Above is the Vue to avoid the abuse of this to read data data in detail, more about Vue to avoid the abuse of this information please pay attention to other related articles on this site!


Related articles: