依赖收集的目的就是为了当修改数据时可以对相关的依赖派发更新
/*** Define a reactive property on an Object.*/export function defineReactive (obj: Object,key: string,val: any,customSetter?: ?Function,shallow?: boolean) {// ...// 当shallow为false时会把新设置的值变成一个响应式对象let childOb = !shallow && observe(val)Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {// ...},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 setterif (getter && !setter) returnif (setter) {setter.call(obj, newVal)} else {val = newVal}childOb = !shallow && observe(newVal)dep.notify() // 通知所有的订阅者}})}
过程分析
当在组件中对响应的数据做了修改就会触发setter,调用dep.notify()方法
定义在src/core/observer/dep.js
/*** A dep is an observable that can have multiple* directives subscribing to it.*/export default class Dep {// ...notify () {// stabilize the subscriber list firstconst 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// ordersubs.sort((a, b) => a.id - b.id)}// 遍历所有的subs,也就是Watcher的实例数组,然后调用每一个watcher的update方法for (let i = 0, l = subs.length; i < l; i++) {subs[i].update()}}}
update方法定义在src/core/observer/watcher.js中
/*** Subscriber interface.* Will be called when a dependency changes.*/update () {/* istanbul ignore else */if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()} else {queueWatcher(this)}}
queueWatcher方法定义在src/core/observer/scheduler.js中
// 引入队列的概念// 不是每次数据改变都触发watcher的回调,而是把这些watcher先添加到一个队列里,然后在nextTick后执行flushSchedulerQueueconst queue: Array<Watcher> = []let has: { [key: number]: ?true } = {} // 保证同一个Watcher只添加一次let waiting = falselet flushing = false/*** 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.idif (has[id] == null) {has[id] = trueif (!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.// flushing == true 用户可能添加了新的watcher// 从后往前找,找到第一个待插入watcher的id比当前队列中watcher的id大的位置,把watcher按照id插入到队列中,因此queue的长度发生了变化let i = queue.length - 1while (i > index && queue[i].id > watcher.id) {i--}queue.splice(i + 1, 0, watcher)}// queue the flushif (!waiting) { // 使用waiting保证对nextTick(flushSchedulerQueue)的调用逻辑只有一次waiting = trueif (process.env.NODE_ENV !== 'production' && !config.async) {flushSchedulerQueue()return}nextTick(flushSchedulerQueue) // 异步执行flushSchedulerQueue}}}
flushSchedulerQueue定义在src/core/observer/scheduler.js中
let flushing = falselet index = 0/*** Flush both queues and run the watchers.*/function flushSchedulerQueue () {currentFlushTimestamp = getNow()flushing = truelet watcher, id// Sort queue before flush.// This ensures that:// 1. Components are updated from parent to child. (because parent is always// created before the child)// 2. A component's user watchers are run before its render watcher (because// user watchers are created before the render watcher)// 3. If a component is destroyed during a parent component's watcher run,// its watchers can be skipped.// 队列排序// 对队列做从小到大的排序,是为了确保以下内容// 1.组件的更新由父到子;因为父组件的创建过程是在子的前边,所以watcher的创建也是先父后子,执行顺序也应该保持先父后子// 2.用户自定义watcher要优先于渲染watcher执行;因为用户自定义watcher是在渲染watcher之前创建的// 3.如果一个组件在父组件的watcher执行期间被销毁,那么它对应的watcher执行都可以被跳过,所以父组件的watcher应该先执行queue.sort((a, b) => a.id - b.id)// do not cache length because more watchers might be pushed// as we run existing watchers// 队列遍历// 遍历时每次都会对queue.length求值,因为在watcher.run()时用户可能会再次添加新的watcher,然后再次执行到queueWatcherfor (index = 0; index < queue.length; index++) {watcher = queue[index]if (watcher.before) {watcher.before()}id = watcher.idhas[id] = nullwatcher.run()// in dev build, check and stop circular updates.if (process.env.NODE_ENV !== 'production' && has[id] != null) {circular[id] = (circular[id] || 0) + 1if (circular[id] > MAX_UPDATE_COUNT) {warn('You may have an infinite update loop ' + (watcher.user? `in watcher with expression "${watcher.expression}"`: `in a component render function.`),watcher.vm)break}}}// keep copies of post queues before resetting stateconst activatedQueue = activatedChildren.slice()const updatedQueue = queue.slice()// 状态恢复resetSchedulerState()// call component updated and activated hookscallActivatedHooks(activatedQueue)callUpdatedHooks(updatedQueue)// devtool hook/* istanbul ignore if */if (devtools && config.devtools) {devtools.emit('flush')}}
resetSchedulerState方法定义在src/core/observer/scheduler.js中
const queue: Array<Watcher> = []const activatedChildren: Array<Component> = []let has: { [key: number]: ?true } = {}let circular: { [key: number]: number } = {}let waiting = falselet flushing = falselet index = 0/*** Reset the scheduler's state.* 把这些控制流程状态的一些变量恢复到初始值,把watcher队列清空*/function resetSchedulerState () {index = queue.length = activatedChildren.length = 0has = {}if (process.env.NODE_ENV !== 'production') {circular = {}}waiting = flushing = false}
watcher.run()的逻辑定义在src/core/observer/watcher.js中
class Watcher {// .../*** Scheduler job interface.* Will be called by the scheduler.*/run () {if (this.active) {// 获取当前值const value = this.get()// 满足新旧值不等、新值是对象类型、deep模式之一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 valueconst oldValue = this.valuethis.value = valueif (this.user) {const info = `callback for watcher "${this.expression}"`// 这就是当添加自定义watcher的时候能在回调函数的参数中拿到新旧值的原因invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)} else {this.cb.call(this.vm, value, oldValue)}}}}// ...}
对于渲染watcher,它在执行this.get()方法求值的时候会执行getter方法
定义在src/core/instance/lifecycle.js中
updateComponent = () => {vm._update(vm._render(), hydrating)}
这就是当去修改组件相关的响应式数据的时候会触发组件重新渲染的原因
接着会重新执行patch的过程,但和首次渲染有所不同
当数据发生变化的时候,触发 setter 逻辑,把在依赖过程中订阅的的所有观察者,也就是 watcher,都触发它们的 update 过程,这个过程又利用了队列做了进一步优化,在 nextTick 后执行所有 watcher 的 run,最后执行它们的回调函数
