Skip to content

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 方法主要做了两件事:

  1. 生成 getter 方法,并没有执行
  2. 如果条件非 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 方法:

  1. 执行 getter 方法,从而执行 dep.depend 方法,将当前 Watcher 实例添加到 Depsubs 数组中。
  2. 如果设置了 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 方法:

  1. dep 添加到 newDeps 数组中。
  2. 如果 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 方法:

  1. 遍历 deps 数组,如果 newDepIds 中没有该依赖,则将该依赖从 deps 数组中移除。
  2. newDepIdsnewDeps 赋值给 depIdsdeps
  3. 清空 newDepIdsnewDeps
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 方法:

  1. this.dirty 设置为 true,表示需要重新计算。
  2. 如果 synctrue,则立即执行 run 方法。
  3. 否则,将 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 方法:

  1. 如果 activetrue,则执行 get 方法。
  2. 如果 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 方法:

  1. lazytrue 时,this.get 方法不会触发,this.valueundefined
  2. 因此提供了 evaluate 方法,以供手动触发依赖。
ts
function evaluate() {
  this.value = this.get()
  this.dirty = false
}

8.depend

depend 方法:

  1. 收集所有依赖
ts
function depend() {
  let i = this.deps.length
  while (i--) {
    this.deps[i].depend()
  }
}

9.teardown

teardown 方法:

  1. 清理 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()
    }
  }
  }