通过上⼀节分析我们了解了响应式数据依赖收集过程,收集的⽬的就是为了当我们修改数据的时候, 可以对相关的依赖派发更新,那么这⼀节我们来详细分析这个过程。
我们先来回顾⼀下 setter 部分的逻辑,对应路径:src/core/observe/index
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
setter 的逻辑有 2 个关键的点,⼀个是 childOb = !shallow && observe(newVal) ,如果 shallow(浅薄) 为 false 的情况,会对新设置的值变成⼀个响应式对象,也就是说初始化的时候数据都监听上了,重新赋值为一个对象,对象里面的数据我们同样需要把他变成响应式数据
app.message
"Hello Vue!"
app.message = {name:122}
app.message.name = 333
另⼀个是 dep.notify() ,通知所有的订阅者,这是本节的关键,接下来我会带⼤家完整的分析整个派发更新的过程。
过程分析
当我们在组件中对响应的数据做了修改,就会触发 setter 的逻辑,最后调⽤ dep.notify() ⽅法, 它 是 Dep 的⼀个实例⽅法,定义在 src/core/observer/dep.js 中:
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
这⾥的逻辑⾮常简单,遍历所有的 subs ,也就是 Watcher 的实例数组,然后调⽤每⼀个 watcher 的 update ⽅法,它的定义在 src/core/observer/watcher.js 中:
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
组件数据更新的场景,会⾛到最后⼀个 queueWatcher(this) 的逻 辑, queueWatcher 的定义在 src/core/observer/scheduler.js 中:
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
这⾥引⼊了⼀个队列的概念,这也是 Vue 在做派发更新的时候的⼀个优化的点,它并不会每次数据改 变都触发 watcher 的回调,⽽是把这些 watcher 先添加到⼀个队列⾥,然后在 nextTick 后执 ⾏ flushSchedulerQueue 。 这⾥有⼏个细节要注意⼀下,⾸先⽤ has 对象保证同⼀个 Watcher 只添加⼀次;接着对 flushing 的判断,else 部分的逻辑稍后我会讲;最后通过 wating 保证对 nextTick(flushSchedulerQueue) 的调⽤逻辑只有⼀次,另外 nextTick 的实现我之后会抽⼀⼩节 专门去讲,⽬前就可以理解它是在下⼀个 tick,也就是异步的去执⾏ flushSchedulerQueue
接下来我们来看 flushSchedulerQueue 的实现,它的定义在 src/core/observer/scheduler.js 中。在对 queue 排序后,接着就是要对它做遍历,拿到对应的 watcher ,执⾏ watcher.run() 。
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
run 函数实际上就是执⾏ this.get()获取当前的值,那么对于渲染 watcher ⽽⾔,它在执⾏ this.get() ⽅法求值的时候,会执⾏ getter ⽅法:
src/core/instance/lifecycle
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
所以这就是当我们去修改组件相关的响应式数据的时候,会触发组件重新渲染的原因,接着就会重新 执⾏ patch 的过程,但它和⾸次渲染有所不同
总结
通过这⼀节的分析,我们对 Vue 数据修改派发更新的过程也有了认识,实际上就是当数据发⽣变化的 时候,触发 setter 逻辑,把在依赖过程中订阅的的所有观察者,也就是 watcher ,都触发它们的 update 过程,这个过程⼜利⽤了队列做了进⼀步优化,在 nextTick 后执⾏所有 watcher 的 run ,最后执⾏它们的回调函数。