依赖收集
src/core/instance/lifecycle.js
之前我们介绍过组件在挂载时执行 $mount
方法,实际执行的是 mountComponent
方法。在这个方法里创建了一个当前实例的渲染 Watcher
对象,第二个参数是 updateComponent
方法,如果该方法被调用会最终执行 _render
方法,_render
会生成 Vnode
并且在这个过程中会产生对实例上数据的访问,也就是触发 getter
。
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
// ...
callHook(vm, 'beforeMount')
let updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
/**
* 创建组件渲染 watcher 并完成依赖收集
* watcher 调用 updateComponent 进而调用 _render 触发了数据访问
* 数据的 getter 中的都有 dep,dep 会把当前 watcher 订阅到该 dep 的 subs 中
*/
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
src/core/observer/watcher.js
Watcher
的构造方法里会调用 get
方法
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 当前 watcher 赋值给 Dep.target
pushTarget(this)
let value
const vm = this.vm
try {
// src/core/instance/lifecycle.js => updateComponent 触发了数据 getter 中 dep
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
首先关注头尾的 pushTarget
和 popTarget
方法,这两个方法保证了在同一时间内只有一个 Watcher
被处理。参数可以为空是因为在某些场景下如错误处理时,需要访问实例上的数据,此时不需要添加 Dep
依赖。
value = this.getter.call(vm, vm)
这句话其实就是调用了上面提到的 updateComponent
方法,调用后会触发数据的 getter
。
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target) // 恢复用
Dep.target = target // 当前 watcher
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
src/core/observer/index.js
上篇文章介绍了 initState
方法,在 initState
调用了 defineReactive
将 data
、 props
做了响应处理。
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep() // 依赖
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 发生数据访问时,如果 Dep.target 存在,那么 Dep.target 就是当前渲染 watcher
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
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() // dep 通知所有组件 watcher
}
})
}
在 getter
中先是判断了 Dep.target
当前对应的 Watcher
是否存在,然后调用 dep.depend()
去进行了依赖收集,告诉针对这个数据的 Dep
把当前 Watcher
添加进来,用于以后通知更新变化。
src/core/observer/dep.js
实际上 dep
和 watcher
是互相添加多对多的关系,到此 Dep
已经添加完需要观测当前数据变化的所有 Watcher
,依赖收集完毕。
-
Dep
addSub (sub: Watcher) { this.subs.push(sub) } depend () { if (Dep.target) { // 实际就是 watcher.addDep 把当前 dep 添加到 watcher // watcher.addDep 也会调用 dep.addSub(this) 把 watcher 添加到 dep 中 Dep.target.addDep(this) } }
-
Watcher
/** * Add a dependency to this directive. */ addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { // 把当前 dep 添加到 watcher dep.addSub(this) } } }
派发更新
当数据发生改变的时候会触发 setter
方法,该方法中会调用 dep.notify()
去通知所有观测此数据的 Watcher
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)
}
// subs 存放所有的 watcher 实例
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
src/core/observer/watcher.js
update
一般会进入 queueWatcher
, 将 Watcher
加入一个任务队列。
/**
* 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 {
// watcher 入队
queueWatcher(this)
}
}
src/core/observer/scheduler.js
queueWatcher
在进行去重后会调用 nextTick
异步的执行队列。每个 watcher
调用 run
方法去触发更新。
-
queueWatcher
/** * 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. // 按照 id 把当前 watcher 添加进队列,id 越小越优先 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 } // 异步执行 flushSchedulerQueue nextTick(flushSchedulerQueue) } } }
-
flushSchedulerQueue
/** * Flush both queues and run the watchers. */ function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let 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. 组件是从父到子更新,因为父组件先与子组件创建 // 2. 用户使用的 watcher api 优先于组件的渲染 watcher,因为是在渲染 watcher 之前创建的 // 3. 如果一个组件在父组件 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 for (index = 0; index < queue.length; index++) { // 每次拿出一个 watcher watcher = queue[index] if (watcher.before) { watcher.before() // callHook(vm, 'beforeUpdate') } id = watcher.id has[id] = null // 移除将要处理的 watcher watcher.run() // 触发更新 } // keep copies of post queues before resetting state const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // call component updated and activated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) }
src/core/observer/watcher.js
最终回到 Watcher
中,Watcher
分为渲染和用户自定义两种,目前我们针对的是渲染 watcer
,最终会再次调用 updateComponent
进行 patch
后生成渲染函数。自定义就是 watcher api
。
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
if (this.active) {
// 对于渲染 watcher 来说 => this.getter.call(vm, vm) => updateComponent => 重新生成渲染函数并进行 patch
// updateComponent 会触发 getter 重新添加 watcher
const value = this.get()
// 用户 watcher 会有返回值,再执行下面
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)
}
}
}
}