当前位置: 首页 > 科技观察

Vue2剥茧-嵌套

时间:2023-03-12 00:23:26 科技观察

响应式系统场景在Vue开发中,肯定会有组件嵌套在组件中的情况,类似下图。

{{text}}
{{text}}
回到我们之前的响应式系统,模拟上面的情况:import{observe}from"./reactive";从“./watcher”导入观察者;constdata={text:"hello,world",inner:"Inner",};observe(data);constupdateMyComponent=()=>{console.log("Subcomponentreceived:",data.inner);};constupdateParentComponent=()=>{newWatcher(updateMyComponent);console.log("Parentcomponentreceived:",data.text);};newWatcher(updateParentComponent);data.text="hello,liang";可以先1分钟考虑一下上面的输出是什么?先回忆一下新的Watcher会做什么。第一步是保存当前函数,然后在执行当前函数之前将全局的Dep.target赋值给当前的Watcher对象。接下来执行getter函数时,如果读取到对应的property,就会触发get,从而将当前的Watcher收集到property的Dep中。执行过程import{observe}from"./reactive";从“./watcher”导入观察者;constdata={text:"hello,world",inner:"internal",};观察(数据);constupdateMyComponent=()=>{console.log("收到子组件:",data.inner);};constupdateParentComponent=()=>{newWatcher(updateMyComponent);console.log("Parentcomponentreceived:",data.text);};newWatcher(updateParentComponent);data.text="hello,liang";让我们逐步阐明:newWatcher(updateParentComponent);将Dep.target分配给保存updateParentComponent函数的Watcher。接下来执行updateParentComponent函数。新观察者(更新我的组件);将Dep.target分配给持有updateMyComponent函数的观察者。接下来执行updateMyComponent函数。constupdateMyComponent=()=>{console.log("Subcomponentreceived:",data.inner);};//读取内部变量。//data.inner的Dep收集当前的Watcher(保存的`updateMyComponent`函数)constupdateParentComponent=()=>{newWatcher(updateMyComponent);console.log("Parentcomponentreceived:",data.text);};//读取文本变量。//data.text的Dep收集了当前的Watcher(保存了`updateMyComponent`函数)data.text="hello,liang";触发text的set函数,执行它依赖的Watcher,此时就是updateMyComponent函数。所以上面代码的最终输出是:子组件接收:internal//newWatcher(updateMyComponent);当输出父组件收到:hello,world//newWatcher(updateParentComponent);当输出子组件接收到:internal//data.text="hello,liang";Output但是子组件不依赖data.text,但是依赖data.text的父组件没有执行。为了解决上述问题,我们在保存当前正在执行的Watcher时使用单个变量。Dep.target=null;//静态变量,全局唯一。回忆一下学习C语言或汇编语言时对函数参数的处理:functionb(p){console.log(p);}functiona(p){b("child");控制台日志(p);}家长”);当一个函数被嵌套调用时,在执行函数a时,我们会先把参数压栈,然后执行函数b,把参数也压栈,函数b执行完后,会把参数pop掉堆。这时候返回a函数就可以正确得到p参数的值了。对应Watcher的集合,我们也可以用一个栈来保存。函数执行前将Watcher入栈,函数执行后将Watcher弹出栈。其中,Dep.target始终指向栈顶的Watcher,代表当前正在执行的函数。回到Dep代码中,我们提供了push和pop方法。import{remove}from"./util";letuid=0;exportdefaultclassDep{...omitted}Dep.target=null;//静态变量,全局唯一//当前正在评估的目标观察者。//这是全局唯一的,因为一次只能评估一个观察者。consttargetStack=[];exportfunctionpushTarget(target){targetStack.推(目标);Dep.target=target;}导出函数popTarget(){targetStack.pop();Dep.target=targetStack[targetStack.length-1];//给栈顶元素赋值}然后在Watcher中,函数执行前压栈,执行完弹出。import{pushTarget,popTarget}from"./dep";exportdefaultclassWatcher{constructor(Fn){this.getter=Fn;this.depIds=newSet();//有一个has函数判断一个id是否存在this.deps=[];这个.newDeps=[];//记录新的依赖this.newDepIds=newSet();这个.get();}/***评估getter,并重新收集依赖项。*/get(){/************修改的地方*******************************/pushTarget(这个);//保存包装当前正在执行的函数的Watcher/********************************************/让值;试试{value=this.getter.call();}catch(e){抛出e;}finally{/**************修改的地方********************************/弹出目标();/**********************************************/这。清理部门();}返回值;}...}测试回到场景开头,然后再次执行:import{observe}from"./reactive";从“./watcher";constdata={text:"hello,world",inner:"Inner",};observe(data);constupdateMyComponent=()=>{console.log("接收到的子组件:",data.inner);};constupdateParentComponent=()=>{newWatcher(updateMyComponent);console.log("Parentcomponentreceived:",data.text);};newWatcher(updateParentComponent);data.text="helloliang";执行newWatcher(updateParentComponent)时;将Watcher压入updateParentComponent函数,执行newWatcher(updateMyComponent);执行newWatcher(updateMyComponent)时;将Watcher压入栈中,执行updateMyComponent函数,data.inner收集当前Dep.target,执行完成后,Watcher出栈,继续执行updateParentComponent函数,data.text收集当前的Dep.target,此时依赖正常,data.text会触发updateParentComponent函数,所以输出如下:Subcomponentreceived:InternalParentComponentreceived:hello,worldsubcomponentreceived:internalparentcomponentreceived:hello,liang今天总结的比较通俗易懂,通过栈来解决嵌套调用的情况。