场景让我们考虑以下代码将输出什么。从“./reactive”导入{观察};从“./watcher”导入观察者;constdata={text:"hello,world",ok:true,};观察(数据);constupdateComponent=()=>{console.log("Received",data.ok?data.text:"not");};newWatcher(updateComponent);//updateComponent执行一次函数,输出hello,worlddata.ok=false;//updateComponent执行一次函数并输出notdata.text="hello,liang";//updateComponent会被执行吗?我们一步步来搞清楚:observer(data)拦截了data中text和ok的get和set,分别初始化一个Dep实例来保存依赖它们的Watcher对象。新观察者(更新组件);这一步会执行updateComponent函数,执行过程中使用到的所有对象属性都会将Watcher收集到对应对象属性中的Dep中。当然这里的Watcher其实也是一样的,所以用了指向它的箭头。数据.ok=false;这一步会触发set去执行Dep中的所有Watcher,然后updateComponent会执行一次。执行updateComponent会重新读取data中的属性,触发get,然后继续采集Watcher。重新执行updateComponent函数时:constupdateComponent=()=>{console.log("received",data.ok?data.text:"not");};因为data.ok的值变成了false,所以不会触发data.text的get,text的Dep也不会改变。而data.ok会继续执行,触发get去收集Watcher,但是因为我们在Dep中使用了一个数组,所以此时收集到的两个Wacher其实是一样的。这里有个问题,会导致updateComponent重复执行。让我们稍后解决它。data.text="你好,小梁";执行这句话的时候会触发text的集合,所以updateComponent会执行一次。但是从代码来看,由于updateComponent函数中data.ok为false,所以data.text对输出没有影响,所以这个执行其实是没有必要的。之所以执行,是因为第一次执行updateComponent读取的是data.text,从而收集到Watcher。第二次执行updateComponent时,虽然没有读取data.text,但是之前的Watcher并没有被清除,所以data.text改变一次后,这个updateComponent还是会执行。所以我们需要的是,如果在重新执行updateComponent时Watcher不再依赖于某个Dep,则将当前的Watcher从Dep中移除。总结这个问题,我们需要做两件事。去重,Dep中不要重复收集Watcher。重置,如果属性对Dep中的Wacher没有影响(也就是说Watcher中的updateComponent将不再读取属性),则从属性的Dep中删除Watcher。去重和去重有两个选项:用Set替换Dep中的subs数组。每个Dep对象都引入了一个id,Watcher对象记录了所有Deps的id。下次重新收集依赖时,如果Dep的id已经存在,则不再收集Watcher。Vue2源码中采用方案2。这里我们实现:Dep类只需要引入id即可。/*************改变*********************************/letuid=0;/*********************************************/exportdefaultclassDep{静态目标;//当前正在执行的函数subs;//依赖函数id;//Depobjectidentifierconstructor(){/**************改变***************************/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
