概念:

     computed是一个计算属性,类似于过滤器,对绑定到view的数据进行处理,计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。它用来监控自己定义的变量,该变量不在data里面声明,直接在computed里面定义,然后就可以在页面上进行双向数据绑定展示出结果或者用作其他处理。

      计算属性不是异步更新的。渲染的时候才能取到最新的值。

原理:

    在initState的时候会初始化initComputed,默认初始化计算属性initComputed的时候会获取用户定义的方法,内部会创建一个watcher(new Watcher),将用户定义的方法传入,这个watcher有一个computedWatcherOptions标识lazy:true,默认创建出来watcher并不会去执行,watcher内部有个属性叫dirty,如果是计算属性默认dirty就是true。watcher的第二次传入的是一个getter用户定义的属性,在watcher对传入的属性方法进行判断,如果是function就把它存到一个getter上,当lazy是true就undefind的什么也不做,false的话就执行this.get()进行依赖追加表示不是计算属性是watch。

       在state文件中有一个defineComputed方法,用来定义计算属性,它的底层也使用的是Object.defineProperty,在这个方法中又定义了一个createComputedGetter方法用来创建getter,在取值的时候会执行createComputedGetter方法返回一个computedGetter函数来,里面会对计算属性的watcher中dirty进行判断,当为true时,watcher才会去执行计算属性进行求值(watcher.evaluate),在evaluate中会执行get方法,get方法就是让当前的getter执行,在求值的过程中进行依赖收集。在取值之前pushTarget(this)就是将watcher放到全局,进行依赖收集,会把当前的计算属性的watcher收集起来,等数据发生变化,就会触发watcher的update执行,在watcher的update中,如果lazy为true是计算属性,就会将dirty改为true在取值时重新进行求值,在求值完成后就会把dirty改为false,如果在取值时dirty为false就会直接将值返回。

      计算属性的核心就是定义了一个dirty,实现了一个缓冲的机制。

源码:

1.initComputed

function initComputed (vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)
  const isSSR = isServerRendering()
  for (const key in computed) {
    const userDef = computed[key] // 获取用户定义的方法
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }
    if (!isSSR) { // 创建一个watcher,把用户的定义的方法传入
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions // {lazy:true},目前watcher不会执行
      )
    }
    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)
      }
    }
  }
}

2.watcher中的部分

    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // lazy默认是true,如果是计算属性默认dirty就是true
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    if (typeof expOrFn === 'function') { // expOrFn就是传进来的用户定义的计算属性方法
      this.getter = expOrFn //如果expOrFn是个函数就将它复制给getter 
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && 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() // lazy为true时就什么也不做

3.defineComputed

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key) // 创建计算属性的getter
      : 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 (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
  // target就是vm,key当前计算属性的key值,sharedPropertyDefinition并不是我们原生的用户定义,而且自己写了一个
}

4.createComputedGetter 

// 定义一个计算属性
function createComputedGetter (key) {
  return function computedGetter () { // 取值的时候回调用此方法
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) { // 查看当前计算属性的dirty是否为true
        watcher.evaluate() // 为true时就会进行求值
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

5.evaluate 

  evaluate () {
    this.value = this.get() // 求值
    this.dirty = false // 求值完将dirty改为false,如果下次是false就直接返回值不做计算
  }

6.get()让当前的getter执行

  get () {
    pushTarget(this) // 将watcher放到全局上
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)  // 取值, 会进行依赖收集,getter就是用户传入的方法
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

7.watcher中的update

  update () { // 追加的依赖发生了变化就会执行这
    if (this.lazy) { // 如果是计算属性,将dirty变为true,当再次取值时就会再次求值
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
  // 计算属性优先于queueWatcher(普通watcher)

 

引申出的面试题:

1.computed,watch,method的区别?

(1)method方法只要把方法用到模板上了,当每次一变化就会重新视图渲染,它的性能开销就比较大。computed计算属性也是一个watcher是具备缓存的,只有当依赖的属性发生变化时才会更新视图。

如果页面中有属性需求区计算不要在页面中这样写{{fn()}},这时候可以写一个计算属性,渲染的时候它依赖的属性没有发生变化,它就不会导致方法重新执行。

(2)computed和watch的原理它们里面都是一个watcher来实现的,computed是具备缓存的,watch不具备。

Logo

前往低代码交流专区

更多推荐