了解vue响应式原理对童鞋,想必对defineReactive方法有印象,这是vue响应式的核心方法。在这个方法,给obj的每个key对应的都new了一个dep,这个dep保存在defineReactive闭包中。这个dep的作用非常明显,收集当前watcher,以便在触发obj属性set方法的时候通知watcher更新。
exportfunctiondefineReactive(obj:Object,key:string,val:any,customSetter?:?Function,shallow?:boolean){constdep=newDep();constproperty=Object.getOwnPropertyDescriptor(obj,key);if(property&&property.configurable===false){return;}//caterforpre-definedgetter/settersconstgetter=property&&property.get;constsetter=property&&property.set;if((!getter||setter)&&arguments.length===2){val=obj[key];}letchildOb=!shallow&&observe(val);Object.defineProperty(obj,key,{enumerable:true,configurable:true,get:functionreactiveGetter(){constvalue=getter?getter.call(obj):val;if(Dep.target){dep.depend();if(childOb){childOb.dep.depend();if(Array.isArray(value)){dependArray(value);}}}returnvalue;},set:functionreactiveSetter(newVal){constvalue=getter?getter.call(obj):val;/*eslint-disableno-self-compare*/if(newVal===value||(newVal!==newVal&&value!==value)){return;}/*eslint-enableno-self-compare*/if(process.env.NODE_ENV!=="production"&&customSetter){customSetter();}//#7981:foraccessorpropertieswithoutsetterif(getter&&!setter)return;if(setter){setter.call(obj,newVal);}else{val=newVal;}childOb=!shallow&&observe(newVal);dep.notify();},});}
但是从上面的defineReactive方法中可以看到,除了obj每个key对应的dep收集依赖(下面称为闭包中dep),还有一个 childOb.dep.depend()在收集依赖。
令人一头雾水的是,在reactiveSetter方法中,只有闭包中的dep在通知watcher, childOb.dep并没有通知watcher,而且这个dep也没必要通知watcher。那 childOb.dep的作用是什么?这里先说作用,是给Vue.set()方法用的。如果还不清楚Vue.set方法的童鞋,建议先了解Vue.set的原理。
我们先看看childOb是什么
从下面代码可以知道,childOb是Observer的实例,在constructor中给childOb添加了dep属性
exportclassObserver{value:any;dep:Dep;vmCount:number;//numberofvmsthathavethisobjectasroot$dataconstructor(value:any){this.value=value;this.dep=newDep();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){constkeys=Object.keys(obj);for(leti=0;i<keys.length;i++){defineReactive(obj,keys[i]);}}observeArray(items:Array<any>){for(leti=0,l=items.length;i<l;i++){observe(items[i]);}}}
我们举个实际的例子看看
data(){return{a:{b:1}}}
在vue初始化,会调用observe对data返回obj进行响应式处理(后面称为dataObj)。接着在new Observer中,会通过walk方法,循环♻️遍历拿到dataObj的每个key,进行defineReactive
exportfunctionobserve(value:any,asRootData:?boolean):Observer|void{if(!isObject(value)||valueinstanceofVNode){return;}letob:Observer|void;if(hasOwn(value,"__ob__")&&value.__ob__instanceofObserver){ob=value.__ob__;}elseif(shouldObserve&&!isServerRendering()&&(Array.isArray(value)||isPlainObject(value))&&Object.isExtensible(value)&&!value._isVue){ob=newObserver(value);}if(asRootData&&ob){ob.vmCount++;}returnob;}
在对a属性defineReactive的时候,这里我们称a属性对应的val为aVal。 此时的childOb就是经过Observer包装过的aVal。childOb.value指向aVal,并且多了一个dep属性用来收集依赖。
下面是最核心的一行代码,通过def又给value(这个value是aVal)添加了ob属性,指向childOb自己。经过这一步骤,我们就可以通过aVal.ob.dep访问到aVal对应的dep。
def(value,"__ob__",this);
众所周知,Vue不能检测到对象属性的添加,为了解决这个问题,Vue添加了一个Vue.set方法。我们看一下set方法
exportfunctionset(target:Array<any>|Object,key:any,val:any):any{if(process.env.NODE_ENV!=="production"&&(isUndef(target)||isPrimitive(target))){warn(`Cannotsetreactivepropertyonundefined,null,orprimitivevalue:${(target:any)}`);}if(Array.isArray(target)&&isValidArrayIndex(key)){target.length=Math.max(target.length,key);target.splice(key,1,val);returnval;}if(keyintarget&&!(keyinObject.prototype)){tarGET@[key]=val;returnval;}constob=(target:any).__ob__;if(target._isVue||(ob&&ob.vmCount)){process.env.NODE_ENV!=="production"&&warn("AvoidaddingreactivepropertiestoaVueinstanceoritsroot$data"+"atruntime-declareitupfrontinthedataoption.");returnval;}if(!ob){tarGET@[key]=val;returnval;}defineReactive(ob.value,key,val);ob.dep.notify();returnval;}
到这里想必大家都恍然大悟了,对某个对象进行Vue.set后,还需要通知watcher去更新。这个对象新增的属性虽然通过defineReactive进行了响应式处理,但是它对应dep存在闭包中,根本取不到。所以最后可以通过这个对象身上的ob对象,取出dep通知对应的watcher更新。
ob.dep.notify();
从上面源码分析我们还可以知道一个注意点⚠️,Vue 不允许动态添加根级响应式属性
this.$set(this.data,”key”,value')
这种写法是?️的,set方法在非生产环境会给出警告
if(target._isVue||(ob&&ob.vmCount)){process.env.NODE_ENV!=="production"&&warn("AvoidaddingreactivepropertiestoaVueinstanceoritsroot$data"+"atruntime-declareitupfrontinthedataoption.");returnval;}
原因就是初始化observe(this.data)的时候,我们无法通过返回的ob收集依赖。