vue2源码-十四、computed和watch的区别

发布时间 2023-04-24 21:40:52作者: 楸枰~

computed和watch的区别

computed watch的相同点。底层都会创建一个 watcher(用法的区别:computed 定义的属性可以在模板中使用,watch 不能在视图中使用)

  • computed 默认不会执行 只有取值的时候才会执行 内部会维护一个 dirty 属性,来控制依赖的值是否发生变化。默认计算属性需要同步返回结果(有个包,让computed变成异步的 0)。

  • watch 默认用户会提供一个回调函数,数据变化了就调用这个回调。我们可以监控某个数据的变化 数据变化了执行某些操作。

computed:

  1. 通过Object.defineProperty实现计算属性的属性劫持;
  2. 在组件实例中创建Watcher实例,用于监听计算属性的依赖项变化;
  3. 在Getter函数中进行依赖收集,即将当前Watcher实例添加到Dep实例中;
  4. 在Setter函数中不进行任何操作,因为计算属性不支持双向绑定;
  5. 通过缓存机制避免重复计算和浪费资源;
  6. 在依赖项发生变化时触发Watcher实例的更新,并通知相关组件进行视图更新。
export function initState (vm: Component) {
  // 省略部分代码
  const computed = options.computed
  if (computed) {
    for (const key in computed) {
      const userDef = computed[key]
      const getter = typeof userDef === 'function' ? userDef : userDef.get
      if (!getter || typeof getter !== 'function') {
        // 省略部分代码
      }
      // 创建Watcher实例,并将其添加到computedWatchers对象中,并设置该计算属性对应的Getter函数
      defineComputed(vm, key, getter, userDef) 
    }
  }
  // 省略部分代码
}

// 定义defineComputed函数,用于创建计算属性的Getter函数及Watcher实例
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function,
  setter?: Function
) {
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = createComputedGetter(key) // 创建计算属性的Getter函数
    sharedPropertyDefinition.set = setter // 设置计算属性的Setter函数(不支持双向绑定)
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? userDef.cache !== false
        ? createComputedGetter(key) // 创建计算属性的Getter函数
        : userDef.get
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

总结一下:

  1. 通过defineComputed函数定义计算属性,并创建计算属性对应的Watcher实例;
  2. createComputedGetter函数中创建计算属性的Getter函数,在其中调用计算属性对应Watcher实例的evaluate方法进行计算,并在needSpend时间过长等情况下设置dirty标记,表示计算结果已经失效,需要重新计算;
  3. 在计算属性的Getter函数中进行依赖收集,并将计算属性的Watcher实例添加到Dep.target对应的Watcher实例的依赖项中;
  4. 在计算属性的Setter函数中不进行任何操作,因为计算属性不支持双向绑定;
  5. 通过缓存机制避免重复计算和浪费资源;
  6. 在依赖项发生变化时触发计算属性的Watcher实例的更新,并通知相关组件进行视图更新。

watch:

  1. 通过initWatch函数遍历watch对象,为每个属性创建Watcher实例,并将其添加到异步更新队列中进行更新;
  2. 通过$watch方法手动创建观察者,内部调用createWatcher函数创建Watcher实例,并设置Watcher实例所依赖的数据项和回调函数等信息;
  3. Watcher类中承担了依赖收集和更新的核心功能;
  4. 在计算属性和watch中都需要为每个依赖项创建Watcher实例,但两者实现方式略有不同。
// 省略部分代码

export function initState (vm: Component) {
  // ...
  const { watch, computed } = options
  if (watch) {
    for (const key in watch) {
      const handler = watch[key]
      if (Array.isArray(handler)) {
        for (let i = 0; i < handler.length; i++) {
          createWatcher(vm, key, handler[i])
        }
      } else {
        createWatcher(vm, key, handler)
      }
    }
  }
  // ...
}

// 省略部分代码

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

// 省略部分代码

实现$watch方法:

export function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

export function stateMixin (Vue: Class<Component>) {
  // ...
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) { // 如果cb是一个对象,则options为传入的第三个参数,cb取该对象的handler属性
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true // 将user标记设置为true,表示当前Watcher实例由用户手动创建
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) { // 如果设置了immediate选项,则立即执行回调函数
      cb.call(vm, watcher.value)
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}