从源码角度分析watch和computed属性

86 阅读1分钟

读本文之前最好对Vue的双向绑定有一定了解

1.普通组件的watch是怎么实现的?

在created生命周期钩子回调函数之前,会执行watch的初始化,在initState方法里面执行initWatch方法,
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)
    }
  }
}
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)
}

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): 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) //创建watcher
    if (options.immediate) { //是否立即执行回调函数
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }

2.普通组件的computed实现方式

computed属性也是在created钩子回调前初始化的,同样是在initState里面调用initComputed实现,
function initComputed (vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null);  // 给当前的vm挂载_computedWatchers属性,
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key] //普通来说是一个方法
    if (!isSSR) {
     // 给每一个computed创建一个computed watcher 注意{ lazy: true }
     // 然后挂载到vm._computedWatchers对象上
      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: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    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
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

//调用Getter为返回值
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 给computed的属性添加订阅watchers
      if (watcher.dirty) {
        watcher.evaluate()
      }
      // 把渲染watcher 添加到属性的订阅里面去,这很关键
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

从上得出,computed其实也是基于watcher实现的,computed是有缓存的(根据dirty判断),而watcher没有,