Vue2响应式系统之分支切换怎么实现(vue,开发技术)

时间:2024-05-09 23:52:44 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

    场景

    我们考虑一下下边的代码会输出什么。

    import{observe}from"./reactive";importWatcherfrom"./watcher";constdata={text:"hello,world",ok:true,};observe(data);constupdateComponent=()=>{console.log("收到",data.ok?data.text:"not");};newWatcher(updateComponent);//updateComponent执行一次函数,输出hello,worlddata.ok=false;//updateComponent执行一次函数,输出notdata.text="hello,liang";//updateComponent会执行吗?

    我们来一步一步理清:

    observer(data)

    拦截了中和的,并且各自初始化了一个实例,用来保存依赖它们的对象。datatextokget、setDepWatcher

    Vue2响应式系统之分支切换怎么实现

    new Watcher(updateComponent)

    这一步会执行函数,执行过程中用到的所有对象属性,会将收集到相应对象属性中的中。updateComponentWatcherDep

    Vue2响应式系统之分支切换怎么实现

    当然这里的其实是同一个,所以用了指向的箭头。Watcher

    data.ok = false

    这一步会触发,从而执行中所有的,此时就会执行一次。setDepWatcherupdateComponent

    执行就会重新读取中的属性,触发,然后继续收集。updateComponentdatagetWatcher

    Vue2响应式系统之分支切换怎么实现

    重新执行函数 的时候:updateComponent

    constupdateComponent=()=>{console.log("收到",data.ok?data.text:"not");};

    因为的值变为,所以就不会触发的,的就不会变化了。data.okfalsedata.textgettextDep

    而会继续执行,触发收集,但由于我们中使用的是数组,此时收集到的两个其实是同一个,这里是有问题,会导致重复执行,一会儿我们来解决下。data.okgetWatcherDepWacherupdateComponent

    data.text = "hello, liang"

    执行这句的时候,会触发的,所以会执行一次。但从代码来看函数中由于为,对输出没有任何影响,这次执行其实是没有必要的。textsetupdateComponentupdateComponentdata.okfalsedata.text

    之所以执行了,是因为第一次执行读取了从而收集了,第二次执行的时候,虽然没有读到,但之前的也没有清除掉,所以这一次改变的时候依旧会执行。updateComponentdata.textWatcherupdateComponentdata.textWatcherdata.textupdateComponent

    所以我们需要的就是当重新执行的时候,如果已经不依赖于某个了,我们需要将当前从该中移除掉。updateComponentWatcherDepWatcherDep

    Vue2响应式系统之分支切换怎么实现

    问题

    总结下来我们需要做两件事情。

    • 去重,中不要重复收集。DepWatcher

    • 重置,如果该属性对中的已经没有影响了(换句话就是,中的已经不会读取到该属性了 ),就将该从该属性的中删除。DepWacherWatcherupdateComponentWatcherDep

    去重

    去重的话有两种方案:

    • Dep中的数组换为。subsSet

    • 每个对象引入,对象中记录所有的的,下次重新收集依赖的时候,如果的已经存在,就不再收集该了。DepidWatcherDepidDepidWatcher

    Vue2源码中采用的是方案这里我们实现下:2

    Dep类的话只需要引入即可。id

    /*************改动***************************/letuid=0;/****************************************/exportdefaultclassDep{statictarget;//当前在执行的函数subs;//依赖的函数 id;//Dep对象标识constructor(){/**************改动**************************/this.id=uid++;/****************************************/this.subs=[];//保存所有需要执行的函数}addSub(sub){this.subs.push(sub);}depend(){if(Dep.target){//委托给Dep.target去调用addSubDep.target.addDep(this);}}notify(){for(leti=0,l=this.subs.length;i<l;i++){this.subs[i].update();}}}Dep.target=null;//静态变量,全局唯一

    在中,我们引入来记录所有的。Watcherthis.depIdsid

    importDepfrom"./dep";exportdefaultclassWatcher{constructor(Fn){this.getter=Fn;/*************改动***************************/this.depIds=newSet();//拥有has函数可以判断是否存在某个id/****************************************/this.get();}/***Evaluatethegetter,andre-collectdependencies.*/get(){Dep.target=this;//保存包装了当前正在执行的函数的Watcherletvalue;try{value=this.getter.call();}catch(e){throwe;}finally{this.cleanupDeps();}returnvalue;}/***Addadependencytothisdirective.*/addDep(dep){/*************改动***************************/constid=dep.id;if(!this.depIds.has(id)){dep.addSub(this);}/****************************************/}/***Subscriberinterface.*Willbecalledwhenadependencychanges.*/update(){this.run();}/***Schedulerjobinterface.*Willbecalledbythescheduler.*/run(){this.get();}}

    重置

    同样是两个方案:

    • 全量式移除,保存所影响的所有对象,当重新收集的前,把当前从记录中的所有对象中移除。WatcherDepWatcherWatcherDep

    • 增量式移除,重新收集依赖时,用一个新的变量记录所有的对象,之后再和旧的对象列表比对,如果新的中没有,旧的中有,就将当前从该对象中移除。DepDepWatcherDep

    Vue2中采用的是方案,这里也实现下。2

    首先是类,我们需要提供一个方法。DepremoveSub

    import{remove}from"./util";/*exportfunctionremove(arr,item){if(arr.length){constindex=arr.indexOf(item);if(index>-1){returnarr.splice(index,1);}}}*/letuid=0;exportdefaultclassDep{statictarget;//当前在执行的函数subs;//依赖的函数id;//Dep对象标识constructor(){this.id=uid++;this.subs=[];//保存所有需要执行的函数}addSub(sub){this.subs.push(sub);}/*************新增************************/removeSub(sub){remove(this.subs,sub);}/****************************************/depend(){if(Dep.target){//委托给Dep.target去调用addSubDep.target.addDep(this);}}notify(){for(leti=0,l=this.subs.length;i<l;i++){this.subs[i].update();}}}Dep.target=null;//静态变量,全局唯一

    然后是类,我们引入来保存所有的旧对象,引入来保存所有的新对象。Watcherthis.depsDepthis.newDepsDep

    importDepfrom"./dep";exportdefaultclassWatcher{constructor(Fn){this.getter=Fn;this.depIds=newSet();//拥有has函数可以判断是否存在某个id /*************新增************************/this.deps=[];this.newDeps=[];//记录新一次的依赖this.newDepIds=newSet(); /****************************************/this.get();}/***Evaluatethegetter,andre-collectdependencies.*/get(){Dep.target=this;//保存包装了当前正在执行的函数的Watcherletvalue;try{value=this.getter.call();}catch(e){throwe;}finally{ /*************新增************************/this.cleanupDeps(); /****************************************/}returnvalue;}/***Addadependencytothisdirective.*/addDep(dep){constid=dep.id;/*************新增************************///新的依赖已经存在的话,同样不需要继续保存if(!this.newDepIds.has(id)){this.newDepIds.add(id);this.newDeps.push(dep);if(!this.depIds.has(id)){dep.addSub(this);}}/****************************************/}/***Cleanupfordependencycollection.*/ /*************新增************************/cleanupDeps(){leti=this.deps.length;//比对新旧列表,找到旧列表里有,但新列表里没有,来移除相应Watcherwhile(i--){constdep=this.deps[i];if(!this.newDepIds.has(dep.id)){dep.removeSub(this);}}//新的列表赋值给旧的,新的列表清空lettmp=this.depIds;this.depIds=this.newDepIds;this.newDepIds=tmp;this.newDepIds.clear();tmp=this.deps;this.deps=this.newDeps;this.newDeps=tmp;this.newDeps.length=0;} /****************************************//***Subscriberinterface.*Willbecalledwhenadependencychanges.*/update(){this.run();}/***Schedulerjobinterface.*Willbecalledbythescheduler.*/run(){this.get();}}

    测试

    回到开头的代码

    import{observe}from"./reactive";importWatcherfrom"./watcher";constdata={text:"hello,world",ok:true,};observe(data);constupdateComponent=()=>{console.log("收到",data.ok?data.text:"not");};newWatcher(updateComponent);//updateComponent执行一次函数,输出hello,worlddata.ok=false;//updateComponent执行一次函数,输出notdata.text="hello,liang";//updateComponent会执行吗?

    此时修改的话就不会再执行了,因为第二次执行的时候,我们把中里的清除了。data.textupdateComponentdata.textDepWatcher

     </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
    本文:Vue2响应式系统之分支切换怎么实现的详细内容,希望对您有所帮助,信息来源于网络。
    上一篇:Vue2响应式系统有什么用下一篇:

    19 人围观 / 0 条评论 ↓快速评论↓

    (必须)

    (必须,保密)

    阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18