Watcher
类是 Vue
响应式系统的核心。
ts
class Watcher {
constructor(
vm: Component, // Vue 实例
expOrFn: string | Function, // 要监听的表达式或函数
cb: Function, // 回调函数
options?: WatcherOptions // 配置选项
) {
// ... 初始化代码
}
// 重要的属性
deep: boolean // 是否深度监听
lazy: boolean // 是否懒执行(用于计算属性)
dirty: boolean // 是否需要重新计算(用于计算属性)
sync: boolean // 是否同步执行
deps: Array<Dep> // 当前 watcher 依赖的所有 dep
// 重要的方法
get() // 获取监听值
addDep(dep: Dep) // 添加依赖
cleanupDeps() // 清理依赖
update() // 当依赖变化时触发更新
run() // 实际执行更新的方法
evaluate() // 评估值(用于计算属性)
depend() // 依赖收集
teardown() // 清理 watcher
}
1. constructor
ts
// 1.支持直接传入getter
new Watcher(null, () => data.msg, (value) => {
console.log(value)
})
// 2.或者传入字符串,利用parsePath方法解析
new Watcher(null, 'data.msg', (value) => {
console.log(value)
})
当 Watcher
实例化时,constructor
方法主要做了两件事:
- 生成
getter
方法,并没有执行。 - 如果条件非
lazy
,则触发this.get
方法。
ts
function constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
if ((this.vm = vm) && isRenderWatcher) {
vm._watcher = this
}
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
if (__DEV__) {
this.onTrack = options.onTrack
this.onTrigger = options.onTrigger
}
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.post = false
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = __DEV__ ? expOrFn.toString() : ''
// parse expression for getter
if (isFunction(expOrFn)) {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
__DEV__ &&
warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy ? undefined : this.get()
}
2.get
get
方法:
- 执行
getter
方法,从而执行dep.depend
方法,将当前Watcher
实例添加到Dep
的subs
数组中。 - 如果设置了
deep
选项,则利用traverse
遍历value
以分别触发相应的getter
方法。
ts
function get() {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e: any) {
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
}
3.addDep
addDep
方法:
- 将
dep
添加到newDeps
数组中。 - 如果
dep
不在depIds
中,则将dep
添加到deps
数组中。
该方法会先判断新依赖是否已经添加,如果没有添加,则将新依赖添加到 newDeps
数组中。
同时,如果旧依赖中没有添加,证明该依赖是初次添加,因此需要再该依赖中订阅当前的 Watcher
实例。
ts
function 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)
}
}
}
4.cleanupDeps
cleanupDeps
方法:
- 遍历
deps
数组,如果newDepIds
中没有该依赖,则将该依赖从deps
数组中移除。 - 将
newDepIds
和newDeps
赋值给depIds
和deps
。 - 清空
newDepIds
和newDeps
。
ts
function cleanupDeps() {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp: any = 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
}
5.update
update
方法:
- 将
this.dirty
设置为true
,表示需要重新计算。 - 如果
sync
为true
,则立即执行run
方法。 - 否则,将
this
添加到queue
数组中。
该方法是依赖订阅的核心方法,当依赖触发时,会调用该方法。
其中 run
方法会同步执行 cb
回调,而 queueWatcher
方法会异步执行 cb
回调。
ts
function update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
6.run
run
方法:
- 如果
active
为true
,则执行get
方法。 - 如果
value
发生变化,则执行cb
回调。
ts
function 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)
}
}
}
}
7.evaluate
evaluate
方法:
- 当
lazy
为true
时,this.get
方法不会触发,this.value
为undefined
。 - 因此提供了
evaluate
方法,以供手动触发依赖。
ts
function evaluate() {
this.value = this.get()
this.dirty = false
}
8.depend
depend
方法:
- 收集所有依赖
ts
function depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
9.teardown
teardown
方法:
- 清理
Watcher
实例。
ts
function teardown() {
if (this.active) {
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
if (this.onStop) {
this.onStop()
}
}
}