# 计算属性的实现

本质上就是一个惰性求值的观察者。回到 src/core/instance/state.js 文件中的 initState 函数,因为计算属性是在这里被初始化的











 


























































































function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  //是否传了computed 
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  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) {//getter为空,警告没有设置对应的getter
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) { //服务端渲染
      // create internal watcher for the computed property.
      const computedWatcherOptions = { computed: true } //标识一个观察者对象是计算属性的观察者
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    //检查计算属性的名字是否已经存在于组件实例对象中
    //初始化计算属性之前已经初始化了 props、methods 和 data 选项,这些选项数据都会定义在组件实例对象上
    if (!(key in vm)) {
      defineComputed(vm, key, userDef) //defineComputed 定义计算属性。
    } 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)
      }
    }
  }
}
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()  //非服务端渲染的情况下计算属性才会缓存值
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get//有get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop//空函数
    sharedPropertyDefinition.set = userDef.set//有set
      ? userDef.set
      : noop
  }
  //没指定set拦截,然后修改报警告
  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)
}

# 计算属性的实现

拦截器为 haredPropertyDefinition.get,非服务端渲染下为

function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key] //观察者对象
     if (watcher.dirty) {//true
        watcher.evaluate()//求值
      }
      if (Dep.target) {
        watcher.depend()//收集依赖
      }
      return watcher.value
  }

Watcherdepend

depend () {
  let i = this.deps.length
    while (i--) {
      this.deps[i].depend()//用来收集依赖
 }
}

watcherconstructor

this.dirty = this.lazy // for lazy watchers
this.value = this.lazy
      ? undefined
      : this.get()
evaluate () {
    //手动求值
    this.value = this.get()
    this.dirty = false
}

# 深度观测



 
 
 








data () {
  return {
    a: {
      b: 1
    }
  }
},
watch: {
  a () {
    console.log('a 改变了')
  }
}

Watcher 实例对象时会读取 a 的值从而触发属性 a 的 get 拦截器函数,最终将依赖收集,没有读取 a.b 属性的值,所以对于 b 来讲是没有收集到任何观察者的,修改 a.b 的值是触发不了响应的
深度观测就是用来解决这个问题的,原理是属性 a.b 中没有收集到观察者,就主动读取一下 a.b 的值,你需要将 deep 选项参数设置为 true,主动告诉 Watcher 实例对象你现在需要的是深度观测
看下 watcherget()

get () {
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    value = this.getter.call(vm, vm)
  } catch (e) {
    if (this.user) {
      handleError(e, vm, `getter for watcher "${this.expression}"`)
    } else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value)
    }
    popTarget()
    this.cleanupDeps()
  }
  return value
}
// traverse  在 src/core/observer/traverse.js
const seenObjects = new Set()

export function traverse (val: any) {
  _traverse(val, seenObjects)
  seenObjects.clear()
}
function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {//不是数组并且不是对象 或者 对象被冻结,或者是vnode实例 不做处理
    return
  }
  if (val.__ob__) {//有__ob__ 如果一个响应式数据是对象或数组,那么它会包含一个叫做 __ob__ 的属性
  //避免死循环
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {//set 里有 return 
      return
    }
    seen.add(depId)
  }
  if (isA) {//数组 递归调用 _traverse 函数
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}

# 同步执行观察者

通常情况下当数据状态发生改变时,所有 Watcher 都为异步执行,这么做的目的是出于对性能的考虑Vue 提供了 Vue.config.async 全局配置,它的默认值为 true,在 src/core/config.js

export default ({
  async: true,
}: Config)

若改为false,所有观察者都将会同步执行

 function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    // 省略...
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {//true 
      //单独执行 flushSchedulerQueue 
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}

dep.notify

notify () {
  // stabilize the subscriber list first
  const subs = this.subs.slice()
  if (process.env.NODE_ENV !== 'production' && !config.async) {
      //执行观察者对象的 update 更新方法之前就对观察者进行排序,从而保证正确的更新顺序
    subs.sort((a, b) => a.id - b.id)
  }
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}