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;i
Utils.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的变化检测问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!