Vue2剥丝抽茧-响应式系统之嵌套
2022-04-02 15:55:31来源:windliang
在 Vue 开发中肯定存在组件嵌套组件的情况,类似于下边的样子。
{{ text }} {{ text }}回到我们之前的响应式系统,模拟一下上边的情况:
import { observe } from "./reactive";import Watcher from "./watcher";const data = { text: "hello, world", inner: "内部",};observe(data);const updateMyComponent = () => { console.log("子组件收到:", data.inner);};const updateParentComponent = () => { new Watcher(updateMyComponent); console.log("父组件收到:", data.text);};new Watcher(updateParentComponent);data.text = "hello, liang";可以先 1 分钟考虑一下上边输出什么?
首先回忆一下 new Watcher 会做什么操作。
第一步是保存当前函数,然后执行当前函数前将全局的 Dep.target 赋值为当前 Watcher 对象。
接下来执行 getter 函数的时候,如果读取了相应的属性就会触发 get ,从而将当前 Watcher 收集到该属性的 Dep 中。
执行过程import { observe } from "./reactive";import Watcher from "./watcher";const data = { text: "hello, world", inner: "内部",};observe(data);const updateMyComponent = () => { console.log("子组件收到:", data.inner);};const updateParentComponent = () => { new Watcher(updateMyComponent); console.log("父组件收到:", data.text);};new Watcher(updateParentComponent);data.text = "hello, liang";我们再一步一步理清一下:
new Watcher(updateParentComponent);将 Dep.target 赋值为保存了 updateParentComponent 函数的 Watcher 。
接下来执行 updateParentComponent 函数。
new Watcher(updateMyComponent);将 Dep.target 赋值为保存了 updateMyComponent 函数的 Watcher 。
接下来执行 updateMyComponent 函数。
const updateMyComponent = () => { console.log("子组件收到:", data.inner);};// 读取了 inner 变量。// data.inner 的 Dep 收集当前 Watcher(保存了 `updateMyComponent` 函数)const updateParentComponent = () => { new Watcher(updateMyComponent); console.log("父组件收到:", data.text);};// 读取了 text 变量。// data.text 的 Dep 收集当前 Watcher (保存了 `updateMyComponent` 函数)data.text = "hello, liang";触发 text 的 set 函数,执行它依赖的 Watcher ,而此时是 updateMyComponent 函数。
所以上边代码最终输出的结果是:
子组件收到: 内部 // new Watcher(updateMyComponent); 时候输出父组件收到:hello, world // new Watcher(updateParentComponent); 时候输出子组件收到: 内部 // data.text = "hello, liang"; 输出然而子组件并不依赖 data.text,依赖 data.text 的父组件反而没有执行。
修复上边的问题出在我们保存当前正在执行 Watcher 时候使用的是单个变量 Dep.target = null; // 静态变量,全局唯一。
回忆一下学习 C 语言或者汇编语言的时候对函数参数的处理:
function b(p) { console.log(p);}function a(p) { b("child"); console.log(p);}a("parent");当函数发生嵌套调用的时候,执行 a 函数的时候我们会先将参数压入栈中,然后执行 b 函数,同样将参数压入栈中,b 函数执行完毕就将参数出栈。此时回到 a 函数就能正确取到 p 参数的值了。
对应于 Watcher 的收集,我们同样可以使用一个栈来保存,执行函数前将 Watcher 压入栈,执行函数完毕后将 Watcher 弹出栈即可。其中,Dep.target 始终指向栈顶 Watcher ,代表当前正在执行的函数。
回到 Dep 代码中,我们提供一个压栈和出栈的方法。
import { remove } from "./util";let uid = 0;export default class Dep { ... 省略}Dep.target = null; // 静态变量,全局唯一// The current target watcher being evaluated.// This is globally unique because only one watcher// can be evaluated at a time.const targetStack = [];export function pushTarget(target) { targetStack.push(target); Dep.target = target;}export function popTarget() { targetStack.pop(); Dep.target = targetStack[targetStack.length - 1]; // 赋值为栈顶元素}然后 Watcher 中,执行函数之前进行入栈,执行后进行出栈。
import { pushTarget, popTarget } from "./dep";export default class Watcher { constructor(Fn) { this.getter = Fn; this.depIds = new Set(); // 拥有 has 函数可以判断是否存在某个 id this.deps = []; this.newDeps = []; // 记录新一次的依赖 this.newDepIds = new Set(); this.get(); } /** * Evaluate the getter, and re-collect dependencies. */ get() { /************修改的地方*******************************/ pushTarget(this); // 保存包装了当前正在执行的函数的 Watcher /*******************************************/ let value; try { value = this.getter.call(); } catch (e) { throw e; } finally { /************修改的地方*******************************/ popTarget(); /*******************************************/ this.cleanupDeps(); } return value; } ...}测试回到开头的场景,再来执行一下:
import { observe } from "./reactive";import Watcher from "./watcher";const data = { text: "hello, world", inner: "内部",};observe(data);const updateMyComponent = () => { console.log("子组件收到:", data.inner);};const updateParentComponent = () => { new Watcher(updateMyComponent); console.log("父组件收到:", data.text);};new Watcher(updateParentComponent);data.text = "hello, liang";执行 new Watcher(updateParentComponent); 的时候将 Watcher 入栈。
进入 updateParentComponent 函数,执行 new Watcher(updateMyComponent); 的时候将 Watcher 入栈。
执行 updateMyComponent 函数,data.inner 收集当前 Dep.target ,执行完毕后 Watcher 出栈。
继续执行 updateParentComponent 函数,data.text 收集当前 Dep.target 。
此时依赖就变得正常了,data.text 会触发 updateParentComponent 函数,从而输出如下:
子组件收到: 内部父组件收到:hello, world子组件收到: 内部父组件收到:hello, liang总结今天这个相对好理解一些,通过栈解决了嵌套调用的情况。
相关新闻
-
中山外贸展现出较强韧性 前三季度全市外贸进出口2187.9亿元
四是优势产业、特色产业推动出口强劲增长。劳动密集型产品、自动数据处理设备及其零部件、灯具照明装置...
-
做一个简易的配置中心,顺带还给整合到了SpringCloud
大家好,我是三友~~最近突然心血来潮(就是闲的)就想着撸一个简单的配置中心,顺便也照葫芦画瓢给整合...
-
为什么JSON.parse会损坏大数字,如何解决这个问题?
从10多年前JSON在线编辑器的早期开始,用户经常反映编辑器有时会破坏他们JSON文档中的大数字的问题。直...
-
在任期第一年 每位CIO都必须完成的12件事
OrlaDaly于2022年3月加入学习软件制造商Skillsoft担任CIO。从第一天起,他的任务就是推动运营效率和公司...
-
一次服务器非法重启后导致的故障排查记录
大家好,我是杰哥。前段时间遇到一个服务器问题:非法重启设备后,服务器进入救援模式,数据盘也不显示...
-
如何在Linux中使用xargs命令
什么是xargs命令xargs命令从标准输入或另一个命令的输出中读取文本行,并将其转换为命令并执行。我们经...
-
聊聊国产数据库TiDB相关知识,你学会了吗?
1、简介 TiDB是由PingCAP公司研发设计的开源分布式HTAP(HybridTransactionalandAnalyticalProce
-
什么是 CDN 缓存命中率以及如何计算和优化它?
本文主要关注AmazonCloudFrontCDN缓存以及如何使用它们来实现更好的缓存命中率。在了解缓存中的命中率...
-
在传统运维监控系统中加入新的预警能力
传统的运维监控系统是以基线为核心判断系统是否存在某个问题并进行告警的。这种模式最大的问题就是基...
-
Kotlin Flow响应式编程,基础知识入门
Kotlin在推出多年之后已经变得非常普及了。相信现在至少有80%的Android项目已经在使用Kotlin开发,或...
-
程序员应如何理解Reactor模式?
大家好,我是小风哥!今天我们聊聊reactor模式。在设计高并发高性能服务器时,一项关键的考虑就是I O...
-
一文掌握所有命令行,包括73个“冷门但有用”的技巧|GitHub 11万标星之作
本文经AI新媒体量子位(公众号ID:QbitAI)授权转载,转载请联系出处。作为程序员,都知道命令行的好处。...
-
一文了解云计算的基本指南
到2021年,超过90%的计算实例和工作负载将使用云数据中心进行处理。毫无疑问,云计算已经开始席卷全球。...
-
LeCun转推,PyTorch GPU内存分配有了火焰图可视化工具
近日,PyTorch核心开发者和FAIR研究者ZacharyDeVito创建了一个新工具(添加实验性API),通过生成和可视...
-
如何提高无线路由器的安全性
众所周知,无线路由器的安全性非常重要,因为无线路由器包含了所有跨网络共享的数据以及网络入口。因此...