preface
In Vue, the data option is a good thing. The data can be read from anywhere in the Vue. But avoid abusing this to read data, and this column will reveal where to avoid it and what happens if you do.
The process of reading data using this
The Vue source code will add getters and setters to the data to make it responsive. The getter function code looks like this:
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
}
Copy the code
When this is used to read data from data, it triggers a getter function that passes var value = getter? getter.call(obj) : val; After obtaining the value, return value is executed to read data.
But in between is a piece of code that goes through a series of complex logical operations to collect dependencies. How to collect dependencies is not covered here, but you can read this column if you want to. We only need to know that dependencies are collected when the dep. target exists.
A conclusion can be drawn from this: when the dep. target is present, using this to read data from the data will collect dependencies. If you abuse this to read data from data, you can repeatedly collect dependencies, causing performance problems.
When does dep. target exist
Dep. Target is assigned by the dependency. A dependency is also called a Watcher or a subscriber. There are three dependencies in Vue, two of which are common: Watch (listener) and Computed (computed attribute). There is also a hidden dependency — the render Watcher, created during the first rendering of the template.
Target is assigned when the dependency is created, which is created using the Watcher constructor.
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;
}
Copy the code
The constructor Watcher executes the instance method get at the end, which is assigned to the dep. target in the instance method GET that executes pushTarget(this).
Dependencies are created when the Vue page or component is first rendered, so the resulting performance problem should be a slow first render.
Where to abuse this to read data from data
Executing code that abuses this to read data in the presence of dep. target can cause performance problems, so it is important to know where the code was written before it was executed. In other words, it is important to know where abusing this to read data in the presence of dep. target can cause performance problems.
In section 2, dep. target is assigned to execute value = this.getter.call(VM, VM), where this.getter is a function, so if you use this to read data, it will collect dependencies. If overused, it can cause performance problems.
The this.getter is assigned during dependency creation, and the this.getter is different for each dependency. Here’s an introduction.
-
The this.getter that watch (listener) depends on is the parsePath function, and its function argument is the object to listen on.
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 } }Copy the code
Passing a and A.B.C as arguments to parsePath returns a function assignment to this.getter. Executing this.getter.call(VM, VM) returns the values of this.a and this.a.b.c. In this process, there will be no misuse of this to read data from data.
}} vm.$watch(' A.B.C ', function(newVal, oldVal){// do something})Copy the code
-
There are two types of this. getters for computed dependencies. If the value of the computed property is a function, then this.getter is the function. If the value of the evaluated property is an object, then this.getter is the value of the object’s get property, which is also a function. There are scenarios where this function can be overused to read data from data. For example, the code looks like this.
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; }}Copy the code
Overuse this to read data in the computed property D. In this case, the value of dep. target is an array. In this case, the value of dep. target is an array. In this case, the value of dep. target is an array. This results in a slow retrieval of the value of the computed attribute D, resulting in a performance problem.
-
The this.getter for rendering Watcher is a function like this:
updateComponent = function() {
vm._update(vm._render(), hydrating);
};
Copy the code
VNode = render. Call (v._renderProxy, vm.$createElement); VNode = render. To give an example of what the render function is.
For example, if template is used:
<template>
<div class="wrap">
<p>{{a}}<span>{{b}}</span></p>
</div>
</template>
Copy the code
The vue-loader will generate the render function, as shown below:
(function anonymous() {
with(this) {
return _c('div', {
attrs: {
"class": "wrap"
}
}, [_c('p', [_v(_s(a)), _c('span', [_v(_s(b))])])])
}
})
Copy the code
For example, with(this){a + b} is equivalent to this.a + this.b. For example, using {{a}} in a template is equivalent to using this to read a from data. Therefore, it is possible to abuse this to read data in the render function generated by the template template. 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>
Copy the code
In the process of using v-for loop list array, this is used to read arR and OBj data in data continuously, making these data carry out a series of complex logical operations to collect this dependency repeatedly, resulting in slow initial rendering speed and performance problems.
4. How to avoid abusing this to read data in data
In summary, overusing this to read data in computed properties and templates can lead to repeated collection of dependencies, which can cause performance problems. How to avoid this?
- How to avoid it in computing properties
The value of the evaluated property is a function whose argument is an instantiated this object of Vue. This can be optimized in the example of overusing this in the evaluated property.
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; }}Copy the code
After the optimization:
computed: { d({ a, b, c, e, f }) { let result = 0; for (let key in a) { if (a[key].num > 20) { result += a[key].num + b + c; } else { result += a[key].num + e + f; } } return result; }}Copy the code
Above, a, B, C, E and f in the data are assigned to the corresponding variables A, b, C, E and f in advance by means of deconstructed assignment. Then, data data can be accessed through these variables in the computed attributes without triggering the dependency collection of corresponding data in the data. In this way, the data in the data is read only once by this and only one dependency collection is triggered, avoiding the performance problems caused by repeated dependency collections.
- How can I avoid this in a template
Preprocess the data used in the V-for loop. Do not read the data of array or object types in the V-for loop. You can do this in the example of abusing this in the template template above.
Assuming that list, ARR, and OBj are all data returned by the server, and that ARR and OBj are not used in any module rendering, this optimization can be done.
Before optimization:
<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>
Copy the code
After the optimization:
<template> <div class="wrap"> <div v-for=item in listData> <div{{item.name}} </div> <div>{{item.age}}</div> </div> </div> </template> <script> export default { data() { return { list: [],}}, created(){this.arr = []; this.arr = []; this.obj = {}; }, computed: { listData: function ({list}) { list.forEach(item => { item.name = this.arr[item.index].name; item.age = this.obj[item.id].age; }) return list; } }, } </script>Copy the code