//先看一例子 importVuefrom'./instance/vue' letv=newVue({ data:{ a:1,b:{ c:3 } } }) console.log(v.b.c);//3 v.$watch("b.c",(newVal,oldVal)=>console.log('newVal',newVal,'oldVal',oldVal,'\n'));//1秒后newVal{d:[Getter/Setter]}oldVal3 v.$watch("b.c.d",'\n'))//2秒后newVal5oldVal4 setTimeout(()=>{ v.b.c={d:4}; },1000) setTimeout(()=>{ v.b.c.d=5 },2000)
(以下都是简化的代码,实际的源码复杂许多)首先是让vue本身对data引用,以及添加$watch方法:
importWatcherfrom'../watcher' import{observe}from"../observer" exportdefaultclassVue{ constructor(options={}){ this.$options=options letdata=this._data=this.$options.data //shallowiteratethroughkeystogivevuedirectreference Object.keys(data).forEach(key=>this._proxy(key)) //deepiteratethroughallkeystoproxyallkeys observe(data,this) } $watch(expOrFn,cb,options){ newWatcher(this,expOrFn,cb) } _proxy(key){ varself=this Object.defineProperty(self,key,{ configurable:true,enumerable:true,get:functionproxyGetter(){ returnself._data[key] },set:functionproxySetter(val){ self._data[key]=val } }) } }
接着实现深度代理函数observe,递归代理所有属性,从而监测所有属性的变化:
import{def}from"../util" importDepfrom"./dep" exportdefaultclassObserver{ constructor(value){ this.value=value this.walk(value) } //deepobserveeachkey walk(value){ Object.keys(value).forEach(key=>this.convert(key,value[key])) } convert(key,val){ defineReactive(this.value,val) } } exportfunctiondefineReactive(obj,val){ vardep=newDep() //recursiveobserveallkeys varchildOb=observe(val) Object.defineProperty(obj,{ enumerable:true,configurable:true,get:()=>{ //checkdependencytosubscribe if(Dep.target){ dep.addSub(Dep.target) } returnval },set:newVal=>{ varvalue=val if(newVal===value){ return } val=newVal //checkchangetoreobserveandpublishalldependencies childOb=observe(newVal) dep.notify() } }) } exportfunctionobserve(value,vm){ if(!value||typeofvalue!=='object'){ return } returnnewObserver(value) }
实现依赖处理:
//import{toArray}from'../util/index' letuid=0 /** *Adepisanobservablethatcanhavemultiple *directivessubscribingtoit. * *@constructor */ exportdefaultfunctionDep(){ this.id=uid++ this.subs=[] } //thecurrenttargetwatcherbeingevaluated. //thisisgloballyuniquebecausetherecouldbeonlyone //watcherbeingevaluatedatanytime. Dep.target=null /** *Addadirectivesubscriber. * *@param{Directive}sub */ Dep.prototype.addSub=function(sub){ this.subs.push(sub) } /** *Removeadirectivesubscriber. * *@param{Directive}sub */ Dep.prototype.removeSub=function(sub){ //this.subs.$remove(sub) } /** *Addselfasadependencytothetargetwatcher. */ Dep.prototype.depend=function(){ Dep.target.addDep(this) } /** *Notifyallsubscribersofanewvalue. */ Dep.prototype.notify=function(){ //stablizethesubscriberlistfirst varsubs=(this.subs) for(vari=0,l=subs.length;i<l;i++){ subs[i].update() } }
实现watch:
importDepfrom'./observer/dep' exportdefaultclassWatcher{ constructor(vm,cb){ this.cb=cb this.vm=vm this.expOrFn=expOrFn this.value=this.get() } update(){ this.run() } run(){ constvalue=this.get() if(value!==this.value){//checkisEqualifnotthencallback this.cb.call(this.vm,value,this.value) this.value=value } } addDep(dep){ dep.addSub(this) } get(){ this.beforeGet(); console.log('\n','watchget'); //fnorexpr varres=this.vm._data,key=[]; console.log('expOrFn',this.expOrFn) //towatchinstancelikea.b.c if(typeofthis.expOrFn=='string'){ this.expOrFn.split('.').forEach(key=>{ res=res[key]//eachwillinvokegetter,sinceDep.targetistrue,thiswillsurelyaddthisintodep }) } this.afterGet(); returnres } } /** *Preparefordependencycollection. */ Watcher.prototype.beforeGet=function(){ Dep.target=this; }; /** *Cleanupfordependencycollection. */ Watcher.prototype.afterGet=function(){ Dep.target=null; };
以上示例是可以无依赖直接运行的。
接下来是vue的源码片段;
一般对象的处理可以直接代理defineProperty就可以了,不过对于Array的各种操作就不管用了,所以vue进行了基本数组方法代理:
functiondef(obj,val,enumerable){ Object.defineProperty(obj,{ value:val,enumerable:!!enumerable,writable:true,configurable:true }) } functionindexOf(arr,obj){ vari=arr.length while(i--){ if(arr[i]===obj)returni } return-1 } constarrayProto=Array.prototype exportconstarrayMethods=Object.create(arrayProto) ;[ 'push','pop','shift','unshift','splice','sort','reverse' ] .forEach(function(method){ //cacheoriginalmethod varoriginal=arrayProto[method] def(arrayMethods,method,functionmutator(){ //avoidleakingarguments: //http://jsperf.com/closure-with-arguments vari=arguments.length varargs=newArray(i) while(i--){ args[i]=arguments[i] } varresult=original.apply(this,args) varob=this.__ob__ varinserted switch(method){ case'push': inserted=args break case'unshift': inserted=args break case'splice': inserted=args.slice(2) break } //sameasobjectchange //checkchangetoreobserveandpublishalldependencies if(inserted)ob.observeArray(inserted) //notifychange ob.dep.notify() returnresult }) }) //es5defineProperty对于数组的某些操作和属性(如:length)变化代理有问题,所以需要使用定义的方法操作 具体原因看http://www.cnblogs.com/ziyunfei/archive/2012/11/30/2795744.html和 http://wiki.jikexueyuan.com/project/vue-js/practices.html def( arrayProto,'$set',function$set(index,val){ if(index>=this.length){ this.length=Number(index)+1 } returnthis.splice(index,1,val)[0]//在里面还是通过代理方法splice实现 } ) def( arrayProto,'$remove',function$remove(item){ /*istanbulignoreif*/ if(!this.length)return varindex=indexOf(this,item) if(index>-1){ returnthis.splice(index,1) } } )
看实际的observer干了什么:
constarrayKeys=Object.getOwnPropertyNames(arrayMethods) exportfunctionObserver(value){ this.value=value this.dep=newDep() def(value,'__ob__',this) if(isArray(value)){ varaugment=hasProto//hasProo='__prop__'in{};__prop__只在某些浏览器才暴漏出来 ?protoAugment//直接将value.__proto__=src :copyAugment//直接改变本身方法 augment(value,arrayMethods,arrayKeys) this.observeArray(value) }else{ this.walk(value) } } functioncopyAugment(target,src,keys){ for(vari=0,l=keys.length;i<l;i++){ varkey=keys[i] def(target,src[key]) } } functionprotoAugment(target,src){ target.__proto__=src }
总的来说,vue是代理了原始数据的增删改查,从而进行事件订阅和发布等操作,从而控制数据流。
heavily inspired by:https://segmentfault.com/a/1190000004384515