I was tired of interviewing recently, but I finally came to an end
preface
Observe, Observer and defineReactive for Vue’s MVVM mode
Vue’s MVVM mode responsivity principle — The special processing of array is not to change the column
The content of this chapter is mainly connected with the above two, interested in the first look ~
- The old rule is the conclusion and the picture first
conclusion
- New Watcher(): is an entry function that needs to specify objects, expressions, and callbacks; Trigger a callback to the response when the expression changes;
- Get (): When reading reactive data, collect the dependency, which is an instance of Watcher;
- Set (): When setting reactive data, the notification depends on the notify of a method on the Watcher instance
1. In the template parsing stage, read the value of obj. A when parsing {{obj. 2. When using watch API;
- The flow chart
Now that all levels of data are already responsive.
So how do you notify all the components that use data when it changes
【 Topic 】 Recall the use of watch in Vue, the principle of which is explained in this chapter.
Watch type: {[key: string] : string | Function | Object | Array} in detail: an Object, the key is to observe the expression of that value is corresponding to the callback Function. The value can also be a method name, or an object that contains options. The Vue instance will call $watch() at instantiation time, iterating through each property of the Watch object. { data: { a: 1, }, watch:{ a: Function (val, oldVal) {console.log('a ', oldVal)},} /* / data.a = 2 /* console */ 'a ', 2, 1}Copy the code
Look at wacht in action and think about how it is implemented internally. Here's a question to think about, and now to answer it step by step.
First, Vue uses a publish and subscribe model, collecting dependencies when reading data and notifying updates when changing data
export default function defineReactive(obj, key, value) {
let childOb = observe(value)
+ let dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get(){+if (Dep.target) {
+ dep.depend()
+ }
}
console.log('Access data triggers get' + 'You are trying to access' + key + 'properties', value)
return value
},
set(newVal) {
if (newVal === value) {
return
}
console.log('Modify data to trigger set' + 'You are trying to modify' + key + 'properties', newVal)
value = newVal
+ dep.notify()
}
})
}
Copy the code
Add this logic to get and set. And the collection dependency is found only if dep.target exists.
Dep and Watcher — who collects the dependencies and who the dependencies are;
Dep depends on the collector
Dep is a class that collects dependencies from instances of the Dep class; You can see that there is a DEP for each of the reactive attributes
let uid = 0;
/** * dep. target is a global flag that can only handle one watcher at a time in Vue
Dep.target = null;
/ * * *@this.id Each Dep instance has an ID that identifies itself *@this.subs Each Dep instance has an array of subs to store watcher * */
function Dep() {
this.id = uid++;
this.subs = [];
}
/** * addSub adds watcher to deP; * called by an instance of Dep@sub Example of Watcher * */
Dep.prototype.addSub = function (sub) {
console.log("addSub");
this.subs.push(sub);
};
/** * Add dep to watcher by calling * depend in get(); * * /
Dep.prototype.depend = function () {
console.log("Collect dependencies in the getter,depend");
Dep.target.addDep(this);
};
/** * Call the update method of each watcher in subs. Component Render Function * */
Dep.prototype.notify = function () {
console.log("notify");
const subs = this.subs.slice();
for (let i = 0, l = subs.length; i < l; i++) { subs[i].update(); }};export { Dep };
Copy the code
Watcher dependencies (subscribers)
Watcher is a class whose instance is a dependency. It is also called a subscriber because it subscribes to some data, such as {{data.a}}, which is a subscription.
import { Dep } from "./Dep";
/ * * *@uid2 is an external variable that identifies the ID * */ of each watcher
let uid2 = 0;
function Watcher(vm, exp, cb) {
this.vm = vm; / / 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴 vm in this article means the data 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴
this.exp = exp; / / 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴 exp expression Such as data. A 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴
this.cb = cb; / / 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴 cb callback function The change in the value of the exp call 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴
this.id = uid2++;
this.depIdsAnddeps = {};
this.getter = parsePath(this.exp);
this.value = this.get(); 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴 every timenewWhen the Watcher will touch 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴} Watcher. Prototype. Update =function () {
/** * the update method performs the cb callback when the view changes. This method actually triggers the view change ** /
let value = this.get();
let oldValue = this.value
this.cb.call(this,value,oldValue);
};
/** * Add dep to watcher ** /
Watcher.prototype.addDep = function (dep) {
if (!this.depIdsAnddeps.hasOwnProperty(dep.id)) {
console.log("addDep");
this.depIdsAnddeps[dep.id] = dep;
dep.addSub(this); }}; Watcher.prototype.get =function () {
let vm = this.vm;
Dep.target = this;
console.log("Dep.target=", Dep.target);
let value = this.getter.call(this, vm);
Dep.target = null;
console.log("Touch end dep. target=", Dep.target);
return value;
};
Copy the code
Three,Dep.target
Make each Watcher correspond exactly to the DEP
-
If you look closely at the code above, you can see that if you don’t do new Watcher() then there are no dependencies to collect. Dep. Target is always null, which makes it impossible to get by if.
-
When the this.get() method is triggered after new Watcher(), the global variable dep.target is first assigned to an instance of the current Watcher. The if judgment passes, and dep. Depend collects the watcher
-
When set() is triggered, the property value is accessed internally again, so get() is triggered again, and with this if judgment, double collection is avoided.
Let’s make a hypothesis
1. Trigger get() with touch data
-
Now all of our data is responsive.
-
At this point, Vue is internally parsing the template, so it must have parsed something like this. {{data.a}} or {{data.b}} etc..
-
New Watcher(vm, expression data.a,cb callback function..) is executed internally. Or new Watcher(vm, expression data.b,cb callback function..)
-
Read data.a and data.b. This triggers the get() of data.a and data.b to establish a corresponding relationship.
- Use a picture to illustrate the process
Set ();
-
{{data.a}} deP and Watcher have established a relationship
-
Data. A = 2
-
When set() is fired, data.a’s DEP calls notify(), which iterates through all the watcher stored in the DEP and calls the update method.
-
The bound callback Function is fired, the Component Render Function, and the view is updated.
(This part involves Compile, you can bind one manually to make it easier to understand)
new Watcher(obj, "data.a".function () {
console.log("Component" + "Render Function" + "View update callback");
});
Copy the code
Console output => “Component” + “Render Function” + “view update callback”, 2, 1
- Use a picture to illustrate the process
5. Now go back to the picture at the beginning
Back to Vue, watch is just a manual new Watcher(), passing in the specified expression and callback. The usage is consistent with the examples in this article.
This article source in my GitHub repository, welcome to visit. mvvm-webpack-demo