VUE使用Object.defineProperty特性劫持传递进来的data对象,把所有property 的getter/setter重写。在getter阶段将依赖者(Watcher)收集起来,在数据变化的setter阶段通知所有Watcher,以此来实现当数据更新组件将自动渲染的“响应式”原理。
在学习Vue响应式原理时,依赖追踪(依赖收集)不算太好理解,会面对“究竟谁依赖谁?谁是依赖者?谁是被依赖者?依赖者在什么地方收集,如何收集?”等一连串问题。
谁是依赖者?谁是被依赖者?
根据官方原理图看对象之间的关系,显然观察者(Watcher)是依赖者,data是被依赖者。Watcher需要被收集起来,当data变化的时候告知Watcher做某些事情,每个组件会对应有一个Watcher,它的作用是保存当依赖关系发生变化时动态执行的函数,比如组件的重新渲染。
学习原理最好的方式还是在浏览器打断点跟踪推栈查看源码最好。当然事先要知道一些概念,如何依赖追踪,观察者的设计模式,什么虚拟DOM等,不然直接看源码会头晕脑胀的。
数据如何被监听劫持
右侧推栈可以看到数据初始化到数据被劫持监听的过程。Observer实例运行的walk方法调用defineReactive$$1做到数据监听。注意的是在数据初始化阶段,依赖并没有收集。
Watcher创建
1.在组件实例生命周期的挂载阶段。可以看到Watcher被创建。
2.从2图看运行了一个赋值语句,将watcher赋值给Dep.target一个全局变量,这是为了在依赖收集阶段做的准备。
图1
图2
依赖在哪里收集,如何收集
1.依赖收集在组件渲染阶段,当组件需要用到(touch)数据时,通过获取前面数据监听定义的值在getter阶段收集依赖。
2.dep是一个依赖收集器,将依赖收集起来,然后在数据变化时通知每个watcher。
疑问点,为什么在getter阶段运行dep.depend(),而不是在创建完watcher后可以在任意地方运行将此收集起来,然后等到数据发生变化时通知wathcer。
如果可以在任意地方收集,则dep的subs成员需要是全局对象,这样当某个数据变化后将会通知所有的watcher,这是不合理的。正确的做法是每个数据属性都有对应得一个Dep依赖收集器,收集相关的watcher。因此只能在getter阶段运行dep.depend(),从设计逻辑上订阅(touch)某个数据的时候才开始收集依赖才是合理的。那么能不能在watcher创建给 Dep.target赋值后,使用obj.defindProperty劫持对象属性呢?显然也是不现实的,当其它Watcher依赖当前data属性时也需要重新重写getter/setter,这会覆盖原来的dep。Data和watcher之间的关系是一对多的依赖关系,而不是一对一。