Vue3 Beta版发布对于放弃 Object.defineProperty API你了解多少?(二)
Observer构造函数
将数据对象转换成响应式数据是Observer构造函数任务之一,一会我们会重点讲this.walk这个方法。现在先来认识下 Dep 。
很多人会把 Dep 理解为订阅者构造函数,但订阅者本身就是一个很抽象的概念,理解上难免会增加心智负担。我更愿意把Dep 理解成一个"容器" 这个"容器"中存储的就是观察者。什么是观察者一会我们来讲。先说下这里的this.dep 就是创建了一个"容器" 这个"容器"中存储的就是某个对象或者数组依赖的观察者。
现在进入到walk中看看:
walk 方法很简单,使用 Object.keys(obj) 获取对象所有可枚举的属性,然后通过 for 循环遍历这些属性,同时为每个属性调用了 defineReactive$$1 函数。
defineReactive$$1函数源码
defineReactive$$1 函数核心就是将数据对象的数据属性转换为访问器属性,但其中做了很多处理边界条件的工作这里我们将不会做过多的阐述。defineReactive 接收五个参数,但是在 walk 方法中调用 defineReactive $$1函数时只传递了前两个参数,数据对象和属性的键名。
重要部分:
需要注意的是:每次调用 defineReactive$$1 都会创建一个 Dep 实例即之前我们讲过的"容器",这里通过闭包的方法让数据对象中每个属性都会对应有一个 "容器" , 这个"容器"会在依赖收集的过程中存储对应的Watcher对象。
shallow 这个属性未传值,它的作用是当未传值或者传递false 那是需要进行深度观测。接下来会在递归调用observe 检测 val 的数据类型是不是引用类型,如果是在把 val 对象中的字段加入到响应式系统当中,用重新调用walk 、defineReactive$$1函数。
看到这里大家就知道为什么说Object.defineProperty很糟糕了吧? 当项目复杂度上升、数据对象结构过于复杂、初始性能将会变得越来越差,而Proxy 能完美的规避掉这些东西。如果你只是看标题进来这里应该能解决你的疑惑了。
但是现在我还想跟大家讲讲关于Dep "容器"的事情。
这是我们给数据对象属性设置的getter, 首先判断Dep.target是否存在,那么Dep.target 是什么呢? 直言不讳的讲,Dep.target中保存的值就是要被收集的依赖(观察者)。所以如果 Dep.target 存在的话说明有依赖需要被收集,这个时候才需要执行 if 语句块内的代码,如果 Dep.target 不存在就意味着没有需要被收集的依赖,所以当然就不需要执行 if 语句块内的代码了。
在 if 语句块内第一句执行的代码就是:dep.depend(),它会将依赖收集到 dep 这个"容器"中,这里的 dep 对象就是属性的 getter/setter 通过闭包关联它自身的那个"容器"。
Dep构造函数代码很简单大家自行去下载源码阅读,我们重点放在Dep.target 上。
Dep.target初始值为null, 那什么时候赋值呢?那我们需要从模板编译组件挂载说起。