继 Observer 和 Dep 类之后,我们迎来了这三个类中最复杂的类——Watcher。Watcher 这个词在 Vue 中有很多叫法:观察者、依赖者及订阅者等,我觉得它们的叫法都挺有道理。Watcher 就像一个哨兵,时刻观察着所需的变量,一有变动就通知其他部件,这里我们也称它为观察者。先来一张 UML 图熟悉下
我们先来看看都有谁使用 Watcher 创建了对象。我们在 Vue 的源码里全局搜索 new Watcher,有三处地方创建了 Watcher 对象,分别是:
1、文件 /src/core/instance/lifecycle.js 的 mountComponent 函数中
...
// 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
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
...
2、文件 /src/core/instance/state.js 的 initComputed 函数中
...
var computedWatcherOptions = { lazy: true };
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
...
3、文件 /src/core/instance/state.js 的 Vue.prototype.$watch 原型方法中
Vue.prototype.$watch = function (expOrFn: string | Function,
cb: any,options?: Object): Function {
...
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
...
}
它们分别用于渲染函数、计算属性(computed)和侦听属性(watch),这三者都是需要在特定变量更新时作出响应。这在观察者模式中,变量是被观察者,Watcher 就代表观察者。也可以说是发布-订阅模式,变量及关联的 Dep 对象是发布者,Watcher 是订阅者。
下面我们来看看 Watcher 的源码,它在文件 /src/core/observer/watcher.js 中定义
...
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component, // Vue类/组件 实例
expOrFn: string | Function, // 字符表达式或函数
cb: Function, // 回调函数,收到更新通知时执行
options?: ?Object, // 其他选项
isRenderWatcher?: boolean // 是否为渲染 watcher
) {
this.vm = vm
if (isRenderWatcher) { // 如果是渲染 watcher,对象赋值给 vm._watcher
vm._watcher = this
}
vm._watchers.push(this) // 对象放入 vm 的 _watchers 中
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb // 回调函数
this.id = ++uid // uid for batching // 实例ID
this.active = true
// 初始化 dirty = lazy,主要用于计算属性
this.dirty = this.lazy // for lazy watchers
this.deps = [] // 当前观察的 dep
this.newDeps = [] // 新收集的需要观察的 dep
this.depIds = new Set() // deps 的ID集合
this.newDepIds = new Set() // newDeps 的ID集合
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
// expOrFn 转成 getter 函数
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
// 如果不是 lazy 的 watcher 则立即执行 get 成员方法
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
// 调用 getter,并且收集依赖
get () {
pushTarget(this)
let value
const vm = this.vm
try {
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
}
/**
* Add a dependency to this directive.
*/
// 添加 dep
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.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
// 清除旧的 dep,新的 dep 赋给 deps
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
// dep派发更新通知时执行
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
/**
* 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) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
// 求值方法,主要在 计算属性 中用
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
// 调用本 watcher 拥有的所有 dep 的 depend 成员方法
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
// watcher 被销毁,清理相关配置
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
Watcher 类有点长,我们先来看构造函数,首先它根据传入构造函数的第五个参数来确定该 Watcher 对象是否为渲染 Watcher,每一个组件都有一个渲染 Wathcer,如果是则把它赋值给 vm._watcher 并放入组件的数组属性 vm._watchers 中。
然后初始化选项,user 是否通过侦听属性创建的 Watcher;lazy 是否为懒求值的 Watcher,通常用于计算属性的延迟求值;dirty 是否脏状态,也是用于延迟求值;cb 通常用于侦听属性的回调函数;sync 接收到更新通知时是否同步执行调度方法;depIds 和 newDepIds 分别为现有 Dep ID 和 新收集的 Dep ID;deps 和 newDeps 分别为现有 Dep 和新收集的 Dep 等。
跟着把传参 expOrFn 转换为 getter 函数,如果 expOrFn 是字符串则通过调用 parsePath 函数转换成函数,它主要是按点切分为属性键用来访问对象的属性值。正如上面提到的,三处不同地方实例化对象时传入的 expOrFn 不同导致各自的 getter 各异,但是它们本质都是引用响应式变量,从而触发依赖收集。
最后判断 this.lazy 的值,假则执行 get 成员方法并返回值赋给 this.value,否则直接赋值 undefined。
成员方法 get 是这个类中非常重要的方法,它主要是调用 getter 函数,并在其执行过程中触发依赖收集。我们先用伪代码简单描述一下
// 入栈,备份当前 Dep.target,然后设置为本 Watcher 对象
pushTarget(this)
// 调用 getter 求值,触发响应式变量的 getter 收集依赖
value = this.getter.call(vm, vm)
// 如果需要,则对 value 的属性递归求值和收集依赖
traverse(value)
// 出栈,恢复 Dep.target 为之前备份的 Watcher 对象
popTarget()
// 新旧依赖过滤,移除不需要的依赖
this.cleanupDeps()
pushTarget 与 popTarget 函数我们在上一节学习 Dep 类的时候有说到,并且还有生动形象地配图。这里它 pushTarget(this) 把自身对象设置到了全局唯一变量 Dep.target,然后调用 this.getter,这个函数简单的理解成引用变量即可,在引用变量的时候会执行在 Object.defineProperty 中配置的变量的 reactiveGetter 函数,并在里面收集依赖。
...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 获取属性的值
const value = getter ? getter.call(obj) : val
// --------------------------------
if (Dep.target) { //
dep.depend() //
if (childOb) { //
childOb.dep.depend() // 依赖收集
if (Array.isArray(value)) { //
dependArray(value) //
} //
} //
} //
// --------------------------------
return value
},
set: function reactiveSetter (newVal) {...}
...
}
然后如果需要深度收集的话,会递归调用属性值求值并收集依赖,完成这一系列之后执行 popTarget 恢复 Dep.target。再之后就是通过调用 cleanupDeps 过滤清理新旧依赖关系,这个过程在方法 cleanupDeps 中细说。
这个方法是用于依赖收集中的,如上代码段,响应式变量的 reactiveGetter 方法中,依赖收集是判断 Dep.target 为真,即是 Dep.target 为 Watcher 对象,则调用变量关联的 Dep 对象的 depend 方法。
// Dep 类 成员方法 depend
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
它相当于又是调用 Watcher 对象的 addDep 方法,并把 Dep 对象传入。
/**
* 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)
// 新的Deps中,且旧的Deps没有,则加入Dep的subs中
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
addDep 检查如果传入的新 Dep 的 id 不在 newDepIds 中则加入并把 Dep 对象加入 newDeps 中。再判断如果 id 不在 depIds 中则调用 Dep 的 addSub 方法,参数为本 Watcher 对象,这个 addSub 只是把 Watcher 对象放入 Dep 的 subs 数组中,这样 Dep 就拥有了订阅了它的所有 Watcher 对象。
在 get 方法中有看到,这个方法是在 popTarget 恢复栈之后被调用,这个时候对于 getter 中新一轮收集的依赖已经通过 addDep 全部暂存于 newDeps 中了,它们的 ID 也存于 newDepIds 中。
/**
* Clean up for dependency collection.
*/
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
// Dep中删除旧的订阅
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
cleanupDeps 方法首先遍历 this.deps 里面旧的 Dep,如果其 ID 不在新的 newDepIds 中,则调用 Dep.removeSub 从 Dep.subs 订阅数组中删除该 Watcher,这样该 Watcher 就不会再接收到更新通知。后面的8行代码,就是对调 newDeps 和 deps,depIds 和 newDepIds,然后清空 newDeps 和 newDepIds,那么,有没有简单的写法呢?为什么又要写这么复杂呢?
this.depIds = this.newDepIds
this.newDepIds = new Set()
this.deps = this.newDeps
this.newDeps = []
这4行代码与上面最后8行代码是等效的,比之却简单了很多,但是 Vue 的写法很大程度降低了频繁地创建对象,不管对调多少次,始终都是用的在构造函数中创建的 Set 对象和数组,这是 Vue 代码的一个很好的细节。
这个方法很好理解,它是供 Dep 在派发更新通知时主动调用的,它根据不同选项做不同处理,如果 lazy 和 sync 都为假,则通过调用 queueWatcher 把自身放入调度队列,在下一个周期执行调度任务 run 方法。简单来说就是计算属性 watcher 是把 dirty 设为 true,不执行回调;渲染 watcher 和 侦听属性 watcher 则是把 watcher 对象放入队列进行调度。
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
// 如果 lazy 为真(延迟求值)
if (this.lazy) {
// 设置 dirty 为真,后续在 evaluate 中计算求值
this.dirty = true
} else if (this.sync) {
// 如果 sync 为真,同步执行调度任务 run 方法
this.run()
} else {
// 把本对象加入调度队列
queueWatcher(this)
}
}
可以称为调度任务,在 update 执行 queueWatcher 放入队列后,在适当的时机会执行该方法,这个细节我们后续会在其他章节说。这个方法会执行方法 get,对如渲染 Watcher 则重新渲染页面并收集依赖,然后比对返回值并判断其他条件觉得是否执行回调函数 this.cb,对于满足条件的侦听属性的回调函数也是在这个阶段被执行。
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
if (this.active) {
// 调用 this.get 求新值
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) {
// 侦听属性则执行用户定义的函数
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
// 执行回调函数
this.cb.call(this.vm, value, oldValue)
}
}
}
}
这个方法用在计算属性的响应式 computedGetter 中,在文件 /src/core/instance/state.js 中见代码
function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
对于计算属性的初始化内容前面我们有学习,计算属性的 Watcher (computedWatcher) 在实例化时 lazy 选项是为真的,那么初始化时 dirty 也为真,这就导致了在 update 和 run 方法中都不会马上调用 get 求值,而是把 dirty 设为 true,并且在引用计算属性的时候,调用计算属性的 computedGetter,这个时候 evaluate 就被调用求值了,并把 dirty 设为 false。
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
是的,没有看错,Watcher 类里面也有一个 depend 方法,这个方法与 evaluate 同样是在计算属性的 computedGetter 中被调用的(computedGetter 代码见 evaluate 方法),当 Dep.target 不为空时则调用它。
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
前面文章“计算属性与侦听属性初始化浅析”中我们研究过计算属性的响应式定义,我们看看它的图简单回顾下:
这个 depend 中会遍历所有依赖的 Dep 并执行其 depend 方法,这有点绕。没关系,其实它的本质就是把计算属性 Watcher(computedWatcher)依赖的所有 Dep 同样也让 Dep.target 依赖之。因为在定义响应式计算属性的时候没有对应关联的 Dep 对象(不像 data 属性),所以 Dep.target 无法收集对计算属性的依赖。computedWatcher.depend() 之后,Dep.target 对于计算属性的 Dep 的依赖转移到对 computedWatcher 依赖的 Dep 上。
Dep.target 在依赖计算属性失败后,转而依赖计算属性依赖的 Dep,这样与直接依赖计算属性是一样的,而且这样还有个好处就是当计算属性依赖的变量有改变时,Dep.target 就能收到更新通知并做必要的响应。
这个方法是在组件销毁或者unwatch($watch方法返回)时调用,它的作用比较简单,就是把自身从 vm._watchers 中移除,并遍历全部依赖的 Dep,调用其 removeSub 从 Dep 的 subs 订阅列表中移除自身。
渲染函数,计算属性和侦听属性等都有需要监视变量变动的需求,Watcher 正是在这种情况下应运而生,Watcher 作为观察者,配合 Dep 一起组成响应式的核心部件。这个类代码量比较多,我们也花了很多笔墨来研究学习,我觉得这是值得的。接下来我们会把前面学的这些类串起来,学习整个响应式的核心流程与逻辑。