Vue源码解读之computed计算属性

前言

通过珠峰课程的学习来理解computed计算属性的原理

那首先呢,先回顾一下vue的响应式数据原理

Vue 在初始化数据时,会给 data 中的所有属性使用 Object.defineProperty 重新定义 setter 和 getter , 当页面获取到对应属性时,会触发 get 方法并进行依赖收集(收集当前组件的watcher) 如果属性发生变化会通知相关依赖进行更新操作 。

其实呢,它们本质上没有什么区别,在前章分享响应式数据原理的时候我们就略带过 computed 计算属性,好,接着往下看:

响应式数据我们知道有个初始化 data 的方法叫做 initData , 那么计算属性当然也有自己的初始化叫做initComputed , 我们走进源码:src/core/instance/state.js 169 行

function initComputed (vm: Component, computed: Object) { // 初始化计算属性
  const watchers = vm._computedWatchers = Object.create(null) // 初始化 计算属性 watcher 列表
  const isSSR = isServerRendering() // 判断是否是服务器渲染

  for (const key in computed) {
    const userDef = computed[key] //注意:获取用户定义的方法
    // 就是计算属性 computed 里边定义的自变量函数   ↑
    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) { // 非服务器渲染
      watchers[key] = new Watcher( // 为每一个计算属性创建一个 watcher
        vm,
        getter || noop, // 将用户定义的方法传入
        noop,
        computedWatcherOptions // 注意:为计算属性options传入 { lazy:true }
      )
    }
    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)
      }
    }
  }
}

export function defineComputed ( // vm  初始化的实例 ↑ 
  target: any,   
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)  // 创建计算属性的 getter 方法实体 ↓ 
      : createGetterInvoker(userDef) // 调用计算属性的 getter 方法实体 ↓
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key) // 创建计算属性的 getter 方法实体 ↓
        : createGetterInvoker(userDef.get) // 调用计算属性的 getter 方法实体 ↓
      : 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) // 响应式数据驱动更新 
}

 // 创建计算属性 getter 方法
function createComputedGetter (key) {
  return function computedGetter () { // 取值的时候回调用此方法
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) { // 做了一个dirty 实现了缓存的机制 , 计算属性 调用对应的 evaluate () 
        watcher.evaluate() // 如是计算属性,则调用 evaluate() ;
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}
// 调用计算属性的 getter 方法
function createGetterInvoker (fn) { 
  return function computedGetter () {
    return fn.call(this, this)
  }
}

总结

通过上边的代码我们可以了解到:initComputed 主要是为每一个计算属性创建一个watcher并且在 options 里边传入 lazy:true 计算属性标识 , 然后再通过defineComputed 将计算属性定义在实例上去进行一个响应式数据观测 。

回顾
再次来到 watcher.jssrc/observer/watcher.js 166行

update () { // 更新数据方法 
    /* istanbul ignore else */
    if (this.lazy) { // 计算属性  依赖的数据发生变化了 会让计算属性的watcher的dirty变成true
      this.dirty = true
    } else if (this.sync) { // 同步watcher
      this.run()
    } else {
      queueWatcher(this) // 将watcher放入队列
    }
  }

src/observer/watcher.js 212行 重点 , 重点 , 重点

evaluate () { // 计算属性时,被调用自身的 get 方法
    this.value = this.get()
    this.dirty = false
}
发布了26 篇原创文章 · 获赞 53 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/HiSen_CSDN/article/details/105476282