Those of you who are familiar with the VUE responsive principle must remember the defineReactive method, which is the core of the VUE responsive method. In this method, each key in obJ is given a new DEP, which is stored in the defineReactive closure. The purpose of this DEP is to collect the current Watcher so that it can notify watcher of updates when the OBj property set method is triggered.
export function defineReactive( obj: Object, key: string, val: any, customSetter? :? Function, shallow? : boolean ) { const dep = new Dep(); const property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return; } // cater for pre-defined getter/setters const getter = property && property.get; const setter = property && property.set; if ((! getter || setter) && arguments.length === 2) { val = obj[key]; } let childOb = ! shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal ! == newVal && value ! == value)) { return; } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV ! == "production" && customSetter) { customSetter(); } // #7981: for accessor properties without setter if (getter && ! setter) return; if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = ! shallow && observe(newVal); dep.notify(); }}); }Copy the code
But as you can see from the defineReactive method above, there is a childob.dep.depend () collecting dependencies in addition to the DEP collecting dependencies for each key of OBJ (called deP in closures below).
The confusing thing is that in the reactiveSetter method, only the DEP in the closure notifes Watcher; childob. dep does not notify Watcher, and the DEP does not need to notify Watcher. So what does childob.deP do? The function is for the vue.set () method. If you are not familiar with the vue. set method, it is recommended to understand the principle of vue. set first.
Let’s see what a childOb is first
As you can see from the following code, childOb is an instance of an Observer. Constructor adds the deP attribute to childOb
export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor(value: any) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, "__ob__", this); if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } this.observeArray(value); } else { this.walk(value); } } walk(obj: Object) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } observeArray(items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]); }}}Copy the code
Let’s do a real example
data(){
return {
a: {
b: 1
}
}
}
Copy the code
During vUE initialization, observe is called to respond to data returning obj (hereafter referred to as dataObj). Next, in the New Observer, each key retrieved from dataObj is iterated through ♻️ via the walk method, and defineReactive is performed
export function observe(value: any, asRootData: ? boolean): Observer | void { if (! isObject(value) || value instanceof VNode) { return; } let ob: Observer | void; if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) { ob = value.__ob__; } else if ( shouldObserve && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && ! value._isVue ) { ob = new Observer(value); } if (asRootData && ob) { ob.vmCount++; } return ob; }Copy the code
In the case of the A attribute defineReactive, we call the val of the A attribute aVal. The childOb at this point is the aVal wrapped by the Observer. Childob. value points to aVal and has an additional DEP attribute to collect dependencies.
Here is the core line of code that adds an __ob__ attribute to value (which is aVal) via def, pointing to childOb itself. After this step, we can access the CORRESPONDING DEP of aVal through aval.ob. dep.
def(value, "__ob__", this);
Copy the code
It is well known that Vue cannot detect the addition of object attributes. To solve this problem, Vue added a vue.set method. Let’s look at the set method
export function set(target: Array<any> | Object, key: any, val: any): any { if ( process.env.NODE_ENV ! == "production" && (isUndef(target) || isPrimitive(target)) ) { warn( `Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}` ); } if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key); target.splice(key, 1, val); return val; } if (key in target && ! (key in Object.prototype)) { target[key] = val; return val; } const ob = (target: any).__ob__; if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV ! == "production" && warn( "Avoid adding reactive properties to a Vue instance or its root $data " + "at runtime - declare it upfront in the data option." ); return val; } if (! ob) { target[key] = val; return val; } defineReactive(ob.value, key, val); ob.dep.notify(); return val; }Copy the code
When you set an object to vue.set, you need to notify Watcher to update it. The attribute added to this object is processed responsively via defineReactive, but its corresponding DEP exists in a closure and cannot be retrieved at all. So we can finally retrieve the deP notification for the corresponding Watcher update through the __ob__ object on this object.
ob.dep.notify();
Copy the code
From the source code analysis above we can also know a note ⚠️, Vue does not allow dynamic root responsive attributes
This. $set (this data, "key", the value ')Copy the code
This is written 🈲️, and the set method will give warnings in non-production environments
if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV ! == "production" && warn( "Avoid adding reactive properties to a Vue instance or its root $data " + "at runtime - declare it upfront in the data option." ); return val; }Copy the code
The reason for this is that we could not collect dependencies from the returned OB when we initialized Observe (this.data).