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).