Vue computed和watch的区别

176 阅读1分钟

定义:

computed选项用于定义计算属性,计算属性是基于其他数据的值动态计算的,并且是在其依赖的数据发生变化时动态更新。 watch选项用于观察和响应Vue实例上数据的变化。它提供一个更灵活的方法处理数据变化,当你需要在数据发生变化时,执行异步操作或负责逻辑时。

共同点:

  1. 每个定义的computed和watch属性都会生成一个watcher。都是基于watcher实现的。

不同点

  1. watch支持异步,computed不支持异步
  2. computed是基于其依赖缓存的,只有在依赖发生变化时才会重新计算。如果没有发生变化,直接返回之前的缓存值,不会重新计算。watch不会缓存值,依赖每次发生变化都会重新执行回调函数

原理:

computed: 实现化watcher,实例化时传入lazy:true。watcher.dirty从lazy取值为true。当调用到计算属性时,第一次就会执行watcher.evaluate(),这个函数执行watcher.get去计算computed的get方法,将计算的值缓存到watcher.value并将watcher.dirty改为false,代表这是最新的值。如果依赖的响应式数据没有发生变化时,dirty就为false,不会重新计算,直接返回watcher.value的值。

const computedWatcherOptions = { lazy: true }

function initComputed(vm: Component, computed: Object) {
    const watchers = (vm._computedWatchers = Object.create(null))
    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
   if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    }
}
export function defineComputed(
  target: any,
  key: string,
  userDef: Record<string, any> | (() => any)
) {
  const shouldCache = !isServerRendering()
  if (isFunction(userDef)) {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  if (__DEV__ && sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter(key) {
  return function computedGetter() {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        if (__DEV__ && Dep.target.onTrack) {
          Dep.target.onTrack({
            effect: Dep.target,
            target: this,
            type: TrackOpTypes.GET,
            key
          })
        }
        watcher.depend()
      }
      return watcher.value
    }
  }
}

watch: 实例化watcher, exporFn参数为监听的属性,handler参数响应式数据发生变化时要执行的函数

function initWatch(vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (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 | (() => any),
  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)
}

Vue.prototype.$watch = function (
    expOrFn: string | (() => any),
    cb: any,
    options?: Record<string, any>
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn() {
      watcher.teardown()
    }
  }