深入 Vue 系列 Vue 中的响应式对象
深入 Vue 系列 Vue 中的依赖收集
深入 Vue 系列 Vue 中的派发更新
简介
通过在defineReactive
观测的data
子项中的getter
函数中完成依赖收集,在defineReactive
观测的data
子项中的setter
函数中完成依赖派发。
首先看一下 defineReactive 中的 setter:
1 | /** |
主要做了两个步:
- 一个是
childOb = !shallow && observe(newVal)
,如果shallow
为false
的情况,会对新设置的值变成一个响应式对象; dep.notify()
dep
对象通知所有的订阅者
执行过程分析
- 执行
dep.notify()
- 执行
watcher.update()
- 执行
queueWatcher(this)
- 执行
nextTick(flushSchedulerQueue)
- 执行
watcher.run()
- 执行
watcher.get()
1. 执行 dep.notify()
当我们对响应式数据做了修改,就会触发 setter
的逻辑,最后调用dep.notify()
方法,它是Dep
的一个实例方法。代码在scr/core/observer.js
中:
1 | class Dep { |
主要做的事情是:
- 遍历所有的
subs
,也就是Watcher
的实例数组,然后调用每一个watcher
的update
方法。
2. 执行 watcher.update()
watcher 类定义在src/core/observer/watcher.js
中:
1 | class Watcher { |
这里的创建时渲染watcher
所以会走 queueWatcher(this)
的逻辑。这里主要做了:
- 判断是否为
computed Watcher
、是否有sync
修饰符,如果都不满足执行queueWatcher
3. 执行 queueWatcher(this)
queueWatcher
的定义在 src/core/observer/scheduler.js
中:
1 | // watcher队列 |
上面代码主要做了如下:
- 把
watcher
添加到一个队列里 - 在
nextTick
后执行flushSchedulerQueue
has 对象保证同一个
Watcher
只添加一次
判断是否为渲染watcher
通过waiting
保证对nextTick(flushSchedulerQueue)
的调用逻辑只有一次
4. 执行 nextTick(flushSchedulerQueue)
nextTick这里单独记录,主要看flushSchedulerQueue
,代码在src/core/observer/scheduler.js
中:
1 | let flushing = false; |
上面的代码主要做了以下几步:
- 队列排序
- .组件的更新由父到子;因为父组件的创建过程是先于子的,所以
watcher
的创建也是先父后子,执行顺序也应该保持先父后子。 - 用户的自定义
watcher
要优先于渲染watcher
执行;因为用户自定义watcher
是在渲染watcher
之前创建的。 - 如果一个组件在父组件的
watcher
执行期间被销毁,那么它对应的watcher
执行都可以被跳过,所以父组件的watcher
应该先执行。
- .组件的更新由父到子;因为父组件的创建过程是先于子的,所以
- 队列遍历
在对queue
排序后,接着就是要对它做遍历,拿到对应的watcher
,执行watcher.run()
。这里需要注意一个细节,在遍历的时候每次都会对queue.length
求值,因为在watcher.run()
的时候,很可能用户会再次添加新的watcher
,这样会再次执行到queueWatcher
,如下:
1 | export function queueWatcher(watcher: Watcher) { |
可以看到,这时候 flushing
为 true
,就会执行到 else
的逻辑,然后就会从后往前找,找到第一个待插入 watcher
的 id
比当前队列中 watcher
的 id
大的位置。把 watcher
按照 id
的插入到队列中,因此 queue
的长度发生了变化。
- 状态恢复
这个过程就是执行resetSchedulerState
函数,它的定义在src/core/observer/scheduler.js
中:
1 | const queue: Array<Watcher> = []; |
逻辑非常简单,就是把这些控制流程状态的一些变量恢复到初始值,把 watcher
队列清空。
5. 执行 watcher.run()
接下来我们继续分析 watcher.run()
的逻辑,它的定义在 src/core/observer/watcher.js
中。
1 | class Watcher { |
主要做了如下:
- 执行
this.getAndInvoke
方法,并传入watcher
的回调函数 getAndInvoke
函数先 通过this.get()
得到它当前的值,然后做判断,如果满足新旧值不等、新值是对象类型、deep 模式任何一个条件,则执行watcher
的回调注意回调函数执行的时候会把第一个和第二个参数传入新值
value
和旧值oldValue
,这就是当我们添加自定义watcher
的时候能在回调函数的参数中拿到新旧值的原因。
6. 执行 watcher.get()
那么对于渲染 watcher 而言,它在执行 this.get() 方法求值的时候,会执行 getter 方法:
1 | updateComponent = () => { |
所以这就是当我们去修改组件相关的响应式数据的时候,会触发组件重新渲染的原因,接着就会重新执行 patch
的过程。
总结
派发更新的过程是:修改数据触发观测数据的setter
方法=>调用dep.notify()
通知所有的订阅者=>循环调用订阅者watcher.update()
=>循环调用watcher.update()
=>调用queueWatcher()
,添加watcher
到一个queue
队列中=>调用nextTick(flushSchedulerQueue)
,对queue
根据id
排序 => 调用watcher.run()
方法 => 触发watcher.get()
方法=>调用vm._update(vm._render(), hydrating)
。