1.首先从computed研究开始

function initComputed (vm: Component, computed: Object) {
  // 首先使用连等方式,声明watchers,并给vue实例上添加_computedWatchers属性,二者指向同一对象,用来记录所有的computed
  const watchers = vm._computedWatchers = Object.create(null)
  // 判断是否是服务端渲染
  const isSSR = isServerRendering()
  for (const key in computed) {
    const userDef = computed[key]
    // computed两种声明方式函数、对象:{get,set}
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (!isSSR) {
      // create internal watcher for the computed property.
      // 给每一个computed创建Watcher实例,并添加到watchers,vm._computedWatchers也会添加
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }
    // 判断vm上是否已存在相同属性名,存在报错,不存在代理到vm上,以便this.xxx调用
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(`The computed property "${key}" is already defined as a method.`, vm)
      }
    }
  }
}

2.defineComputed
1) 首先看下noop函数是什么?

// noop就是一个空函数
export function noop (a?: any, b?: any, c?: any) {}

2) 看一下createComputedGetter函数

function createComputedGetter (key) {
  // 例: computedName(){return '我的名字是' + this.name}
  // 返回一个getter函数,将computed属性(computedName)的get指向到watcher.value,
  // 这里的watcher.value会触发watcher.prototype.get方法,进而触发computed中依赖响应式变量的get方法,
  // 然后触发dep.depend(),将watcher添加到该变量闭包中dep实例的subs数组
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // computed的默认会传lazy:true, watcher.dirty初始值等于lazy,所以这里是true
      if (watcher.dirty) {
        //this.value 获取值,并且把dirty设置为false,这样就可以起到缓存的作用,多次访问同一个computed,只会触发一次watcher.get()
        // evaluate () {
        //   this.value = this.get()
        //   this.dirty = false
        // }
        watcher.evaluate() // 翻译是估值,求函数的值
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

3) 最后看一下最终的defineComputed函数,这里会用到noop和createComputedGetter,所以在上面提前了解一下

// 例: computedName(){return '我的名字是' + this.name}
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  // 判断是否是服务端渲染,这里只研究客户端渲染的情况
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    // 客户端渲染时shouldCache为true,也就是会将computedName.get设置为createComputedGetter(),
    // createComputedGetter会返回一个getter方法,见下方
    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
  }
  // 如果computed属性是function时,被设置时会报错,这就是我们平时为什么computed不能被设置的原因
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  // 将computed属性(computedName)定义到vm上,这里的target就是vm(vue实例)
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

fengfengyouyou
8 声望0 粉丝