Those of you who have read the official VUE documentation should already be familiar with this diagram.
How is the responsiveness of VUE implemented?
I’ve heard too many answers, through Object.defineProperty, but when asked in detail, the other party didn’t know.
Lu to respect first
const Observer = function(data) {
// Loop change to add get set for each attribute
for (let key indata) { defineReactive(data, key); }}const defineReactive = function(obj, key) {
// the local variable dep is used for the get set internal call
const dep = new Dep();
// Get the current value
let val = obj[key];
Object.defineProperty(obj, key, {
// Sets the current description property to recyclable
enumerable: true.// Set the current description property to be modified
configurable: true,
get() {
console.log('in get');
// Call addSub in the dependency collector to collect the dependencies of the current property and Watcher
dep.depend();
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// When the value changes, notify the dependent collector, update each Watcher that needs to be updated,
// Where does each need to be updated by what determination? dep.subsdep.notify(); }}); }const observe = function(data) {
return new Observer(data);
}
const Vue = function(options) {
const self = this;
// Assign data to this._data, the source code part of the Proxy so we use the simplest temporary implementation
if (options && typeof options.data === 'function') {
this._data = options.data.apply(this);
}
// Mount function
this.mount = function() {
new Watcher(self, self.render);
}
// Render function
this.render = function() {
with(self) { _data.text; }}/ / to monitor this. _data while forming
observe(this._data);
}
const Watcher = function(vm, fn) {
const self = this;
this.vm = vm;
// Point the current dep. target to yourself
Dep.target = this;
// Add the current Wathcer to the Dep method
this.addDep = function(dep) {
dep.addSub(self);
}
// Update method used to trigger vm._render
this.update = function() {
console.log('in watcher update');
fn();
}
Vm. _render is called for the first time to trigger get for text
// To associate the current Wathcer with the Dep
this.value = fn();
// Dep. Target is cleared to prevent notify from repeatedly binding Watcher to Dep.
// Cause code to loop forever
Dep.target = null;
}
const Dep = function() {
const self = this;
// Collect the target
this.target = null;
// Stores the Watcher that needs to be notified in the collector
this.subs = [];
// Bind the relationship between Dep and Wathcer when there is a target
this.depend = function() {
if (Dep.target) {
// Self.addSub (dep.target),
// I didn't write this because I wanted to restore the source code process.Dep.target.addDep(self); }}// Add Watcher for the current collector
this.addSub = function(watcher) {
self.subs.push(watcher);
}
// Notify all wathcers in the collector to call their update method
this.notify = function() {
for (let i = 0; i < self.subs.length; i += 1) { self.subs[i].update(); }}}const vue = new Vue({
data() {
return {
text: 'hello world'
};
}
})
vue.mount(); // in get
vue._data.text = '123'; // in watcher update /n in get
Copy the code
Here we have implemented a simple VUE response in less than 100 lines of code. Of course, if you don’t think about the process, I’m sure you can do it in 40 lines of code. But I don’t want to omit it here. Why? I am afraid that you will automatically ignore the process, and people will ask you something about it, but they will be speechless when they read it. Anyway, FOR your own good, drink plenty of hot water.
What does Dep do?
Dependency collector, this is not the official name clam, I named it myself, just to make it easier to remember.
Let’s look at two examples of dependency collectors in action.
-
Example 1: Is rendering meaningless unnecessary?
const vm = new Vue({ data() { return { text: 'hello world'.text2: 'hey',}}})Copy the code
Text2 is called again when the vm.text2 value changes, but text2 is not used in the template, so is it pointless to handle render here?
In the render function of Vue, we call the value that is relevant to the render. Therefore, values that are not related to the render will not trigger get and will not be added to the listener in the dependency collector (addSub does not trigger). Subs in notify is also empty. OK, go back to the regression demo, let’s do a little test to confirm what I said.
const vue = new Vue({ data() { return { text: 'hello world'.text2: 'hey' }; } }) vue.mount(); // in get vue._data.text = '456'; // in watcher update /n in get vue._data.text2 = '123'; // nothing Copy the code
-
Example 2: Who is notified when multiple Vue instances reference the same data? Should we call both of them?
let commonData = { text: 'hello world' }; const vm1 = new Vue({ data() { returncommonData; }})const vm2 = new Vue({ data() { return commonData; } }) vm1.mount(); // in get vm2.mount(); // in get commonData.text = 'hey' In watcher update /n in get Copy the code
I hope that through these two examples, you have a general understanding of the function of Dep. Do you have a feeling that it is the same as before? I wish I had. To recap (the following dependency collector is actually Dep) :
vue
willdata
Initialize to aObserver
And for every value in the object, we overwrite the valueget
,set
.data
Each of thekey
Each has a separate dependent collector.- in
get
Add a listener to the dependent collector - At mount, an instance was created
Watcher
, which points the target of the collector to the presentWatcher
- in
data
Triggered when the value changesset
, triggers updates that depend on all listeners in the collectorWatcher.update
If that’s not enough, check out my other articles
“Hand in hand with you through the VUE part of the source code”
What vue did to Template