Writing is not easy, point a like bai brothers focus on Vue source sharing, the article is divided into vernal version and source version, vernal version to help understand the working principle, source version to help understand the internal details, let us study together based on Vue version [2.5.17]
If you think the layout is ugly, please click the following link or pull to the following to follow the public account can also be
[Vue principle] Computed – source version
Today I’m recording the computed source code, and sometimes I wonder why I’m recording the source code again. Now I’ve figured it out
After a while, you forget where your so-called understanding came from
“Ah, why will do so, the relationship why is so, I C….”
Thus, documenting and simplifying the source code helps us quickly identify the source, solve our doubts, and enhance our understanding
All right.
Well, it’s a long and detailed article, so be prepared to read it
We highlight the source code implementation of several issues
How to calculate computed? What is the cache of computed? When is computed initializedCopy the code
Questions are not resolved sequentially, because they are related to each other, and as you explore the source code, you will find answers
To begin today’s exploration with this question, take a look at the source code
When is it initialized
function Vue() {... Other processing initState(this)... Parse the template to generate a DOM insert pagefunction initState(vm) {
var opts = vm.$options;
if(opts.computed) { initComputed(vm, opts.computed); }... }Copy the code
Yes, when you call Vue to create an instance, you’re dealing with various options, including computed
The way to handle computed is to initComputed, and I present the source code below
initComputed
function initComputed(vm, computed) {
var watchers = vm._computedWatchers =
Object.create(null);
for (var key in computed) {
var userDef = computed[key];
var getter =
typeof userDef === 'function'? userDef: userDef.get; Watchers [key] = new watcher (VM, getter, {lazy:true}); // Check whether there is an attribute with the same nameif (! (key invm)) { defineComputed(vm, key, userDef); }}}Copy the code
The initComputed code does several things
1. Allocate watcher for each computed
2, defineComputed processing
3. Collect all computed Watcher
All right, three things, one thing at a time
1. Allocate watcher for each computed
What is wrong with Computed and Watcher?
Save the computed function. 2. Save the computed result. 3Copy the code
Take a look at the Watcher source constructor
function Watcher(vm, expOrFn, options) {
this.dirty = this.lazy = options.lazy;
this.getter = expOrFn;
this.value = this.lazy ? undefined: this.get();
};
Copy the code
So from this source code, let’s see what parameters were passed on Computed
new Watcher(vm, getter, { lazy: true })
Copy the code
So, we know the above three of the mystery is how to return a responsibility
1. Save the getters for the Settings.
Save the user-set computed-getter to watcher.getter for later computations
2. Watcher. value stores the result of the calculation, but there is a condition that because of lazy, the instance is not created and the value is read immediately
This is an optimization of Vue where you have to read computed again and start computing instead of initialization
Although we did not start the calculation, we still calculate the value of the watcher.get method. Take a look at the source code (omitted part of the code, other issues will be shown in more detail below).
This method, which simply executes the saved getter function to get the calculated value, is as simple as it gets
Watcher.prototype.get = functionVar value = this.getter.call(vm, vm);return value
};
Copy the code
3. On a computed watcher, the value of Lazy is entered
Yes, the function is to cache the result of the calculation, not to have to recalculate every time it is used
Here, we assign lazy to dirty. Why?
Because lazy represents a fixed description that cannot be changed, it means that the watcher needs caching
If dirty is true, the cache is dirty and needs to be recalculated. Otherwise, the cache is not used
Dirty is false by default, and lazy gives dirty an initial value, indicating that your cache control task has started
So remember, dirty is really the key to controlling the cache, and lazy is just an open function
How do you control the cache
2, defineComputed processing
Please look at the source code
functionDefineComputed (target, key, userDef) {// setsetThe default value is used to avoid computedset
var set = function(){} // If the user has setset, using the user'sset
if (userDef.set) set= userDef.set object.defineProperty (target, key, {get:createComputedGetter(key),set:set
});
}
Copy the code
The source code has been much shorter by me, but the meaning remains the same
1. Use object.defineProperty to compute attributes on an instance, so you can access them directly
2, the set function is null by default, if the user set, the user set
The createComputedGetter wrapper returns the get function
So the point is, number three, why is it important?
Two big problems are solved here, [matchmaking problem + cache control problem]
Send the createComputedGetter source code immediately
function createComputedGetter(key) {
return functionVar watcher = this._computedwatchers [key]; var watcher = this._computedwatchers [key]; // If computed dependent data changes, dirty becomestrueTo recalculate and then update the cache value watcher.valueif(watcher.dirty) { watcher.evaluate(); } // Here is the focus of Yue Yue computed matchmaking, let both sides establish a relationshipif (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
Copy the code
1. Cache control
The following code is used for cache control, please read below
if (watcher.dirty) {
watcher.evaluate()
}
Copy the code
Watcher. evaluate is used to recalculate, update the cache value, and reset dirty to false to indicate that the cache has been updated
Here’s the source code
Watcher.prototype.evaluate = function() { this.value = this.get(); // Immediately after the update function is executed, reset the flag bit this.dirty =false;
};
Copy the code
2. Evaluate only if dirty is true
So you can control the cache by controlling the dirty, but how do you control the dirty?
For example, computed data A refers to data B. That is, computed data A depends on B. Therefore, WATcher of A is collected by B
When B changes, it will notify A to update, that is, call a-watcher.update, look at the source code
Watcher.prototype.update = function() {
if (this.lazy) this.dirty = true; . There are other irrelevant operations that have been omitted};Copy the code
When computed is notified of the update, the dirty is simply set to true, and when comptued is read, evalute is called to recalculate
Matchmaker matchmaking
The meaning of matchmaking, also made clear in the vernacular version, here simply say
The -p, computed- C, and data- D pages are available
2. Theoretically, when D changes, C will change, and C will notify P to update. 3. In fact, C lets D establish a connection with P, and lets P be notified directly when D changesCopy the code
That’s right, because of this code
if (Dep.target) {
watcher.depend();
}
Copy the code
You don’t look at this code is short ah, involves a lot of content really ah and need some brains, see the source code minutes around not come over, really take especially big how to write out
Take a look at the source code for watcher.depend
Watcher.prototype.depend = function() {
var i = this.deps.length;
while (i--) {
// this.deps[i].depend();
dep.addSub(Dep.target)
}
};
Copy the code
That’s what this is for! (Still use the PCD code in the above example)
Let D’s dependency collector collect dep. target, and what is dep. target currently?
Yes, the watcher of the page!
So in this case, D will collect the page watcher, so it will notify the page watcher directly
Look tired…..
Why, you might ask, is dep. target a page watcher?
Hi, there’s a little bit more to this and a little bit more to this. Sit down, die
First send you a source code gift, good
Watcher.prototype.get = functionVar value = this.getter.call(this.vm, this.vm); var value = this.getter.call(this.vm, this.vm); // Restore the previous watcher popTarget()return value
};
Dep.target = null;
var targetStack = [];
functionPushTarget (_target) {// Cache the last dep. target for later recoveryif (Dep.target) {
targetStack.push(Dep.target);
}
Dep.target = _target;
}
function popTarget() {
Dep.target = targetStack.pop();
}
Copy the code
Note a few words
Getter saves the page update function. Computed Watcher. getter saves the computed getter
Watcher. get executes the watcher.getter and sets the dep. target
Dep.target will have a cache
The following begins the detailed process of matchmaking
1. When a page is updated and computed is read, dep. target is set to page watcher.
2. Computed is read. The function wrapped in createComputedGetter is triggered
Evaluted is called, in turn computed-watcher.get is called, dep. target is set to computed-watcher, and the old value page watcher is cached.
3. Computed computing reads data, and then computed watcher collects data
In addition, computed- Watcher is saved to deP, the dependent collector of data (for the next step).
On the computed, dep. target is released and the last watcher (page watcher) is restored.
4, manually watcher.depend, let data collect Dep. Target again, so data collected to restore the page watcher
One additional data changes the process
In summary, the data dependency collector = [computed-watcher, page watcher]
When data changes, the page is notified in order of traversal, and the computed is updated first, then the page is updated, so that the page can read the latest computed value
3. Collect all computed Watcher
From the source code, you can see that after each computed watcher is created, it is all collected into an object and hung on the instance
Why collect, my temporary idea is
To get the corresponding watcher in the createComputedGetter
You could pass the watcher, but what you do here is you pass the key, and you use the key to find the watcher
Ouch, my mother, finally finished, look how many words, more than 7000, write me
Off-site: Nima is most of the source code good
Okok, line, line, line