派发更新主要是在我们修改响应式数据的时候可以得到通知。
当修改响应式对象地数据时,会触发 setter ,里面有两个关键点,一个是 childOb = !shallow && observe(newVal)
,如果 shallow
为 false 的情况下,会对新设置的值变成响应式对象;另一个是 dep.notify()
,通知所有订阅者。
class Dep {
// ...
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
dep.notify
方法是遍历 subs
中的每一个 watcher
分别执行 update
方法。我们来看一下 watcher
中的实现。
在我们当前的逻辑主线中,我们主要分析一般组件数据更新的场景,所以会走到最后一个逻辑 queueWatcher(this)
中,那我们就需要看一下 queueWatcher
中到底发生了什么?在 src/core/observer/cheduler.js
中,我们可以看到其定义:
该方法主要负责推入一个 watcher
到 watcher queue
中,在队列中如果已经存在相同的 id 则该 watcher
将被跳过,除非它是在队列被刷新时被推送。
这里 Vue 出于性能上的考虑,在派发更新的时候并不会立即触发 watcher
的回调函数,而是把这些 watcher
先添加到一个队列中,然后在 nextTick
后执行 flushSchedulerQueue
。
需要注意的是,我们在调用 flushSchedulerQueue
之前,会先做一系列的拦截判断处理,首先用 has
对象来保证同一个 watcher
只添加一次;接着对 flushing
和 wating
的判断,保证对 nextTick(flushSchedulerQueue)
的调用只有一次。
-
队列排序
queue.sort((a, b) => a.id - b.id)
对队列做了从小到大的排序,这么做主要确保以下几点:
1,组件的更新是从上到下,从父到子的,所以父组件的创建过程是先于子组件,所以watcher
的创建也是先父后子,那么我们回调函数的watcher
的执行顺序也应该保持先父后子。
2,用户的自定义watcher
要优先于渲染watcher
执行,因为用户自定义的watcher
是在渲染watcher
之前创建的。
3,如果一个组件的父组件的watcher
在执行期间被销毁,那么其对应的watcher
执行都会被跳过,所以父组件的watcher
应该先执行。其实,终究到底,就是确保先创建的
watcher
先执行。 -
队列遍历
在对queue
排序后,接着就要遍历了,依次对queue
中的每个watcher
执行watcher.run()
。需要特别注意的是,在遍历的时候需要每次对queue.length
求值,因为在我们watcher.run()
的时候用户可能再次添加新的watcher
,那久会执行到queueWatcher
,如下: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) } // ... } }
可以看到,这时候的
flushing
为 true,就会执行到 else 的逻辑,然后就会从后往前找,找到第一个待插入的watcher
的 id 比当前队列中的watcher
的 id 大的位置。把wathcer
按照 id 的插入到队列中,所以queue
的长度也发生了改变。
最后我们来看一下 watcher.run()
的逻辑。
class Watcher {
/**
* 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)
}
}
}
}
}
执行 watcher.run()
,先通过 this.get()
拿到当前的值,然后做判断,如果满足新旧值不等、新值是对象类型、deep
模式任何一个条件,则执行 watcher
的回调,注意回调函数执行的时候会把第一个和第二参数传入新值 value
和旧值 oldValue
,这就是我们自定义 watcher
的时候可以在回调函数中拿到新旧值的原因。这是 this.user = true
的 watcher
,那么对于渲染 watcher
而言,在执行 this.get()
的时候,会执行 getter
方法:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
这就是为什么当我们修改组件相关的响应式数据的时候,会触发组件重新渲染的原因。接着就会执行 patch
过程,但是和组件的首次渲染略有不同。