Vue 源码解析 双向绑定
整体流程
vue2.x 源码
- Data 通过 observer 转换成了 getter/setter 的形式来追踪变化
- 当外界通过 Watcher 读取数据时,会触发 getter 从而将 Watcher 添加到依赖中
- 当数据发生了变化时,会触发 setter,从而向 Dep 中的依赖 (即 Watcher) 发送通知
- Watcher 接收到通知后,向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等
Observer
监测数据,给数据打上响应式标记:
__ob__
在 getter 中收集依赖,在 setter 中通知依赖更新
1 | export class Observer { |
Dep
依赖收集器
- 渲染页面时碰到插值表达式,v-bind 等需要数据的地方,会实例化一个 watcher
- 实例化 watcher 就会对依赖的数据求值,从而触发 getter,数据的 getter 函数就会添加依赖自己的 watcher,从而完成依赖收集
- 可以理解为 watcher 在收集依赖,而代码的实现方式是在数据中存储依赖自己的 watcher
1 | export class Dep { |
Watcher
逻辑生成依赖,并添加到 Dep 中
Watcher 先把自己设置到全局唯一的指定位置 (window.target),然后读取数据。触发这个数据的 getter。接着,在getter 中就会从全局唯一的那个位置读取当前正在读取数据的 Watcher,并把这个 watcher 收集到 Dep 中去。收集好之后,当数据发生变化时,会向 Dep 中的每个 Watcher 发送通知
1 | export class Watcher { |
问题理解
只要触发 getter 就会收集依赖吗
不会
- 在 Dep 的 depend 方法中,只有 Dep.target 为真时才会添加依赖。比如在派发更新时会触发 watcher 的 update 方法,该方法也会触发 parsePath 来取值,但是此时的 Dep.target 为 null,不会添加依赖
- 仔细观察可以发现,只有 watcher 的 get 方法中会调用 pushTarget(this) 来对 Dep.target 赋值,其他时候 Dep.target 都是 null,而 get 方法只会在实例化 watcher 的时候调用
- 因此,在实现中,一个 watcher 的依赖在其实例化时就已经确定了,之后任何读取值的操作均不会增加依赖
依赖嵌套的对象属性
1 | let w2 = new Watcher(obj, "b.m.n", (val, oldVal) => |
w2 会依赖 obj.b.m.n,但是 w2 会依赖 obj.b, obj.b.m 吗?或者说,obj.b 和 obj.b.m,它们闭包中保存的 dep 中会有 w2 吗
会
- 如果让 obj.b = null,那么很显然 w2 的回调函数应该被触发,这就说明 w2 会依赖中间层级的对象属性
- 代码层面: new Watcher() 时,会调用 watcher 的 get 方法,将 Dep.target 设置为 w2,get 方法会调用 parsePath 来取值
- 局部变量 obj 为对象 obj,读取 obj.b 的值,触发 getter,触发 dep.depend() (该 dep 是 obj.b 的闭包中的 dep),Dep.target 存在,添加依赖
- 局部变量 obj 为 obj.b,读取 obj.b.m 的值,触发 getter,触发 dep.depend() (该 dep 是 obj.b.m 的闭包中的 dep),Dep.target 存在,添加依赖
- 局部变量 obj 为对象 obj.b.m,读取 obj.b.m.n 的值,触发 getter,触发 dep.depend() (该 dep 是 obj.b.m.n 的闭包中的 dep),Dep.target 存在,添加依赖
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Nanyin の 小屋!
评论