Vue中基于 Observer、Dep、Watcher 三个类实现了观察者模式
new Vue → _init() → initState → initData → observe → new Observer(data) → Observer.walk(data) → defineReactive → Object.defineProperty(obj, key)
Observer 类
通过调用链可以发现在 initData 开始,由 Observer 开始对 data 的每个 key 值进行 set、get 的拦截监听 (利用 Object.defineProperty),同时可以发现在 defineReactive 中对每个 key 值的拦截监听都会创建一个独立的 Dep 对象,在 get 的时候调用 dep.depend() 进行依赖收集,在 set 的时候调用 dep.notify() 进行派发更新(通知观察者更新视图),我们的数据就是被观察者
function defieneReactive(obj, key, val) {
const dep = new Dep();
...
Object.defineProperty(obj,key, {
...
get: function reactiveGetter () {
if(Dep.target){
dep.depend();
...
}
return val
}
set: function reactiveSetter () {
dep.notify()
}
...
})
}
Dep 类
每个属性都增加一个 dep 收集器,负责收集观察者 watcher,将 watcher 对象维护在自己的 subs 属性中,以及通知观察者 watcher 进行 update 更新操作
1、定义 subs 数组,当劫持到数据访问时,执行 dep.depend(),通知 watcher 订阅 dep,然后在 watcher 内部执行 dep.addSub(),通知 dep 收集 watcher
2、当劫持到数据变更时,执行 dep.notify() ,通知所有的观察者 watcher 进行 update 更新操作
class Dep {
static target;
subs;
consructor () {
...
this.subs = [];
}
addSub (sub) {
this.subs.push(sub);
}
removeSub(sub) {
remove(this.sub, sub);
}
depend () {
if(Dep.target) {
Dep.target.addDep(this);
}
}
notify () {
const subs = this.subs.slice();
for(let i = 0; i < subs.length; i++){
subs[i].update();
}
}
}
// 清空当前渲染的 watcher,静态变量
Dep.target = null
Dep 是一个 class,其中有一个关键的静态属性 target,全局唯一,Dep.target 指向的是当前正在执行的 watcher 实例,
这是一个非常巧妙的设计!因为在同一时间保证了只能有一个全局的 watcher 被计算
另一个属性 subs 则是一个 Watcher 的数组,所以 Dep 实际上就是对 Watcher 的管理
**注意:**
渲染/更新完毕后我们会立即清空 Dep.target,保证了只有在模版渲染/更新阶段的取值操作才会进行依赖收集。之后我们手动进行数据访问时,不会触发依赖收集,因为此时 Dep.target 已经重置为 null
Watcher 类
在 Vue 中存在三种 Watcher,分别是:
Render Watcher(渲染 Watcher)
、Computed Watcher(computed 属性的 Watcher)
和 User Watcher(watch 属性的 Watcher)
渲染 Watcher 实例化的时候参数传递为 true 表示
computed 属性的 Watcher 实例化的时候参数传递 { lazy: true } 表示
watch 属性的 Watcher 实例化的时候参数传递 { user: true } 表示
为观察者,负责订阅 dep,并在订阅的同时执行 dep.addSub() 让 dep 同步收集当前 watcher。当接收到 dep 的通知时,执行 update 重新渲染视图
class Watcher {
getter;
...
constructor(vm, expression){
...
this.getter = expression;
this.get();
}
get () {
pushTarget(this);
value = this.getter.call(vm,vm)
...
return value
}
addDep (dep) {
...
dep.addSub(this)
}
...
}
function pushTarget (_target) {
Dep.target = _target
}
Watcher 是一个 class,它定义了一些方法,其中和依赖收集相关的主要有 get、addDep 等
get 方法中的 pushTarget 实际上就是把 Dep.target 赋值为当前的 watcher
this.getter.call(vm, vm),这里的 getter 会执行 vm._render() 方法,在这个过程中会触发数据对象的 getter。
那么每个对象值的 getter 都有有一个 dep,在触发 getter 的时候会调用方法 dep.depend() 方法,也就是会执行 Dep.target.addDep(this)。
刚才 Dep.target 已经被赋值为 watcher,于是便会执行 addDep 方法,然后走到 dep.addSub() 方法,
便将当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目的是为了后续数据变化时候能通知到哪些subs 做准备。
所以在 vm._render() 过程中,会触发所有数据的 getter,这样便已经完成了一个依赖收集的过程
Dep 和 Watcher 的关系
Dep 的 subs 数组存放这个数据所绑定的观察者对象,观察者对象的 deps 数组中存放着与这个观察者有关的数据 Dep。所以数据的 Dep 与 Watcher 其实是多对多关系
dep 和 watcher 是多对多的关系。每个组件都对应一个渲染 watcher,每个响应式属性都有一个 dep 收集器。一个组件可以包含多个属性(一个 watcher 对应多个 dep),一个属性可以被多个组件使用(一个 dep 对应多个 watcher)
vue组件中数据更新会发生什么事情?
由于上面我们同步了一些关键信息,通过断点结合,可以看到整个调用过程
1、数据更新,我们首先会触发 Data 的 set 拦截,同时调用 dep 的 notify 方法。
2、遍历 dep 对象中的 watcher 数组,执行 watcher 的 update 方法。
3、在 watcher 的 update 函数中调用 queueWatcher 方法,并将自己作为入参。
4、在 queueWatcher 函数中,判断 has map 是否有当前 watcher id,没有则将 watcher 存入 queue 队列,记录 watcher id 到 has map 中,调用 nextTick 方法;有则跳过这一步,等待异步刷新队列执行(异步刷新队列:flushSchedulerQueue)。
5、在 nextTick 中,将 flushSchedulerQueue push 进 callbacks 数组,执行 timerFunc 方法。
6、timerFunc 中等待浏览器的微任务/宏任务回调时候遍历执行 callbacks 数组中的异步刷新队列方法 flushSchedulerQueue。
7、在 flushSchedulerQueue 中,调用 watcher 的 run 方法。
8、在 watcher 的 run 中,调用 watcher 的 get 方法。
9、在 watcher 的 get 中,调用 watcher 的 getter 属性方法。
10、在 Watcher 对象初始化时候,getter 就是 mountComponent 时候传入 updateComponent 方法。
11、执行 updateComponent 方法,会调用组件 _update 方法,传入当前组件 _render 函数返回值。
12、在 _render 函数中,获取当前组件 data 中的值(当前最新的值)。
13、在 _update 中,调用 patch 方法,传入新旧 Vnode。
14、在 patch 中,调用 patchVnode 方法,开始 diff 比较虚拟 dom 树。
网友评论