ES6 Proxy实现Vue的变化检测问题
Vue变化检测Object使用DefineProperty、数组使用方法拦截实现。最近,Vue3.0将采用ES6Proxy的形式重新实现Vue的变化检测,在官方还没给出新方法之前,我们先实现一个基于Proxy的变化检测。
模块划分
参照之前Vue变化检测的代码,将Vue变化检测的功能分为以下几个部分。
- Observer
- Dep
- Watcher
- Utils
首先,我们要确定的问题是,将Dep依赖搜集存在哪里。Vue2.x里,Object的依赖收集放在defineRactive,Array的依收集存入到Observer中。ES6Proxy里,考虑到让handler访问dep,我们将依赖放入到Observer中。
Observer
observer.js功能代码如下:
importDepfrom'./dep'; import{isObject}from'./utils'; exportdefaultclassObserver{ constructor(value){ //递归处理子元素 this.obeserve(value); //实现当前元素的代理 this.value=this.proxyTarget(value); } proxyTarget(targetBefore,keyBefore){ constdep=newDep(); targetBefore.__dep__=dep; letself=this; constfiltersAtrr=val=>['__dep__','__parent__'].indexOf(val)>-1; returnnewProxy(targetBefore,{ get:function(target,key,receiver){ if(filtersAtrr(key))returnReflect.get(target,key,receiver); if(!Array.isArray(target)){ dep.depend(key); } //sort/reverse等不改变数组长度的,在get里触发 if(Array.isArray(target)){ if((key==='sort'||key==='reverse')&&target.__parent__){ target.__parent__.__dep__.notify(keyBefore); } } returnReflect.get(target,key,receiver); }, set:function(target,key,value,receiver){ if(filtersAtrr(key))returnReflect.set(target,key,value,receiver); //新增元素,需要proxy const{newValue,isChanged}=self.addProxyTarget(value,target,key,self); //设置key为新元素 Reflect.set(target,key,newValue,receiver); //notify self.depNotify(target,key,keyBefore,dep,isChanged); returntrue; }, }); } addProxyTarget(value,target,key,self){ letnewValue=value; letisChanged=false; if(isObject(value)&&!value.__parent__){ self.obeserve(newValue); newValue=self.proxyTarget(newValue,key); newValue.__parent__=target; isChanged=true; } return{ newValue, isChanged, } } depNotify(target,key,keyBefore,dep,isChanged){ if(isChanged&&target.__parent__){ target.__parent__.__dep__.notify(keyBefore); return; } if(Array.isArray(target)){ if(key==='length'&&target.__parent__){ target.__parent__.__dep__.notify(keyBefore); } }else{ dep.notify(key); } } obeserve(target){ //只处理对象类型,包括数组、对象 if(!isObject(target))return; for(letkeyintarget){ if(isObject(target[key])&&target[key]!==null){ this.obeserve(target[key]); target[key]=this.proxyTarget(target[key],key); //设置__parent__,方便子元素调用 target[key].__parent__=target; } } } }
在Observer中,针对对象,只需要执行 dep.depend(key)、 dep.notify(key)即可。添加key是为了能正确的触发收集,不知道怎么说明白为什么要这样做,只能一切尽在不言中了。
Array,如何实现依赖的收集和触发那。依赖收集与Object类似, dep.depend(key)完成数组的收集。关于触发,可以分为两个方面,一是改变数组长度、二未改变数组长度的。改变数组长度的,在set里,通过长度属性的设置触发父级元素的notify。为什么要使用父级元素的notify那?我们可以分析以下,在你设置数组的长度时,这时候的target\key\value分别是[]\length*,这个时候,数组的依赖收集是没有的,你watcher的是数组,并不是数组本身。这个时候只能通过 target.__parent__.__dep__.notify(keyBefore)触发父级的收集,完成数据变化的检测。二对于未改变数组长度的,这里的做法,虽然是直接 target.__parent__.__dep__.notify(keyBefore)触发依赖,但是有个严重的问题,实际上更新的数据不是最新的,这个地方暂时还没想到比较好的方法,欢迎大家讨论。
Dep
Dep.js
letuid=0; exportdefaultclassDep{ constructor(){ this.subs={}; this.id=uid++; } addSub(prop,sub){ this.subs[prop]=this.subs[prop]||[]; this.subs[prop].push(sub); } removeSub(prop,sub){ this.remove(this.subs[prop]||[],sub); } depend(prop){ if(Dep.target){ //传入的是当前依赖 Dep.target.addDep(prop,this) } } notify(prop){ constsubs=(this.subs[prop]||[]).slice(); for(leti=0,l=subs.length;i-1){ returnarr.splice(index,1); } } } } Dep.target=null consttargetStack=[] exportfunctionpushTarget(_target){ if(Dep.target)targetStack.push(Dep.target) Dep.target=_target } exportfunctionpopTarget(){ Dep.target=targetStack.pop() }
dep添加prop实现类型的绑定,为什么要这么做那?使用proxy代理后,你假如wahcter对象下的几个元素,此时的deps将同时存在这几个元素,你触发依赖的时候,这些依赖都会执行。因此,通过key值绑定观察事件,触发时,能完成对象的正确触发。
watcher、utils
import{parsePath}from'./utils'; import{pushTarget,popTarget}from'./dep' exportdefaultclassWatcher{ constructor(vm,expOrFn,cb){ //depid集合 this.depIds=newSet(); this.vm=vm; this.getter=parsePath(expOrFn); this.cb=cb; this.value=this.get(); } get(){ pushTarget(this); letvalue=this.getter.call(this.vm,this.vm); popTarget(); returnvalue; } update(){ constoldValue=this.value; this.value=this.get(); this.cb.call(this.vm,this.value,oldValue); } addDep(prop,dep){ constid=dep.id; if(!this.depIds.has(id)){ this.depIds.add(id); dep.addSub(prop,this); } } }
utils.js
/** *解析简单路径 */ constbailRE=/[^\w.$]/; exportfunctionparsePath(path){ if(bailRE.test(path)){ return; } constsegments=path.split('.'); returnfunction(obj){ for(leti=0;iUtils.js/Watchers.js与Vue2.x类似,这里就不多介绍了。
测试一下
test.js
importObserverfrom'./observer'; importWatcherfrom'./watcher'; letdata={ name:'lijincai', password:'***********', address:{ home:'安徽亳州谯城区', }, list:[{ name:'lijincai', password:'youknowitObject', }], }; constnewData=newObserver(data); letindex=0; constwatcherName=newWatcher(newData,'value.name',(newValue,oldValue)=>{ console.log(`${index++}:namenewValue:`,newValue,',oldValue:',oldValue); }); constwatcherPassword=newWatcher(newData,'value.password',(newValue,oldValue)=>{ console.log(`${index++}:passwordnewValue:`,newValue,',oldValue:',oldValue); }); constwatcherAddress=newWatcher(newData,'value.address',(newValue,oldValue)=>{ console.log(`${index++}:addressnewValue:`,newValue,',oldValue:',oldValue); }); constwatcherAddressHome=newWatcher(newData,'value.address.home',(newValue,oldValue)=>{ console.log(`${index++}:address.homenewValue:`,newValue,',oldValue:',oldValue); }); constwatcherAddProp=newWatcher(newData,'value.addProp',(newValue,oldValue)=>{ console.log(`${index++}:addPropnewValue:`,newValue,',oldValue:',oldValue); }); constwatcherDataObject=newWatcher(newData,'value.list',(newValue,oldValue)=>{ console.log(`${index++}:newValue:`,newValue,',oldValue:',oldValue); }); newData.value.name='resetName'; newData.value.password='resetPassword'; newData.value.name='helloworldname'; newData.value.password='helloworldpassword'; newData.value.address.home='hellohome'; newData.value.address.home='hellohome2'; newData.value.addProp='helloaddProp'; newData.value.addProp={ name:'ceshi', }; newData.value.addProp.name='ceshi2'; newData.value.list.push('1'); newData.value.list.splice(0,1); newData.value.list.sort(); newData.value.list.reverse(); newData.value.list.push('1'); newData.value.list.unshift({name:'nihao'}); newData.value.list[0]={ name:'lijincai', password:'youknowitArray', }; newData.value.list[0].name='youknowitarrayafter'; newData.value.list.pop(); newData.value.list.shift(); newData.value.list.length=1;我们使用对象、数组测试一下我们的ES6Proxy检测。
20:17:44.725index.js?afc7:200:namenewValue:resetName,oldValue:lijincai 20:17:44.725index.js?afc7:241:passwordnewValue:resetPassword,oldValue:*********** 20:17:44.725index.js?afc7:202:namenewValue:helloworldname,oldValue:resetName 20:17:44.725index.js?afc7:243:passwordnewValue:helloworldpassword,oldValue:resetPassword 20:17:44.726index.js?afc7:324:address.homenewValue:hellohome,oldValue:安徽亳州谯城区 20:17:44.726index.js?afc7:325:address.homenewValue:hellohome2,oldValue:hellohome 20:17:44.726index.js?afc7:366:addPropnewValue:helloaddProp,oldValue:undefined 20:17:44.727index.js?afc7:367:addPropnewValue:Proxy{name:"ceshi",__dep__:Dep,__parent__:{…}},oldValue:helloaddProp 20:17:44.727index.js?afc7:400:newValue:Proxy{0:Proxy,1:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:Proxy,1:"1",__dep__:Dep,__parent__:{…}} 20:17:44.728index.js?afc7:401:newValue:Proxy{0:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:"1",__dep__:Dep,__parent__:{…}} 20:17:44.729index.js?afc7:402:newValue:Proxy{0:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:"1",__dep__:Dep,__parent__:{…}} 20:17:44.731index.js?afc7:403:newValue:Proxy{0:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:"1",__dep__:Dep,__parent__:{…}} 20:17:44.734index.js?afc7:404:newValue:Proxy{0:"1",1:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:"1",1:"1",__dep__:Dep,__parent__:{…}} 20:17:44.735index.js?afc7:405:newValue:Proxy{0:Proxy,1:"1",2:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:Proxy,1:"1",2:"1",__dep__:Dep,__parent__:{…}} 20:17:44.735index.js?afc7:406:newValue:Proxy{0:Proxy,1:"1",2:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:Proxy,1:"1",2:"1",__dep__:Dep,__parent__:{…}} 20:17:44.736index.js?afc7:407:newValue:Proxy{0:Proxy,1:"1",2:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:Proxy,1:"1",2:"1",__dep__:Dep,__parent__:{…}} 20:17:44.737index.js?afc7:408:newValue:Proxy{0:Proxy,1:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:Proxy,1:"1",__dep__:Dep,__parent__:{…}} 20:17:44.738index.js?afc7:409:newValue:Proxy{0:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:"1",__dep__:Dep,__parent__:{…}} 20:17:44.738index.js?afc7:4010:newValue:Proxy{0:"1",__dep__:Dep,__parent__:{…}},oldValue:Proxy{0:"1",__dep__:Dep,__parent__:{…}}我们看到了ES6Proxy后实现了Object/Array的检测,虽然还存在一些问题,但是基本的侦测变化的功能都已经具备了。
总结
以上所述是小编给大家介绍的ES6Proxy实现Vue的变化检测问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!