jyzwf / blog

在Issues里记录技术得点滴
17 stars 3 forks source link

Vue里的 Computed #46

Open jyzwf opened 6 years ago

jyzwf commented 6 years ago

computed 是 Vue 中常用的一个属性,具体见计算属性和侦听器

现在我们就以官网里的例子来进行源码探秘

var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})

首先 computed 的初始化是在 state.js 里的 initState方法中,该方法不仅初始化了 computed ,还初始化了 props、data、methods、watch


/*初始化props、methods、data、computed与watch*/
export function initState(vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  /*初始化props*/
  if (opts.props) initProps(vm, opts.props)
  /*初始化方法*/
  if (opts.methods) initMethods(vm, opts.methods)
  /*初始化data*/
  if (opts.data) {
    initData(vm)
  } else {
    /*该组件没有data的时候绑定一个空对象*/
    observe(vm._data = {}, true /* asRootData */ )
  }
  /*初始化computed*/
  if (opts.computed) initComputed(vm, opts.computed)
  /*初始化watchers*/
  if (opts.watch) initWatch(vm, opts.watch)
}

现在我们就来看看 initComputed方法

const computedWatcherOptions = {
  lazy: true
}

function initComputed(vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)

  for (const key in computed) {    // 遍历computed 对象里的元素
    const userDef = computed[key]
    /*
      计算属性可以是 `function`,也可以是有 `get/set` 属性的对象。
    */
    let getter = typeof userDef === 'function' ? userDef : userDef.get    // 判断对应的计算属性值是否是函数还是设置了 `get/set` 属性的对象,并获取函数
    if (process.env.NODE_ENV !== 'production') {
      if (getter === undefined) {
        warn(
          `No getter function has been defined for computed property "${key}".`,
          vm
        )
        getter = noop    // function noop(){}
      }
    }
    // create internal watcher for the computed property.
    /*
      为计算属性创建一个内部的监视器Watcher,保存在vm实例的_computedWatchers中
      这里的computedWatcherOptions参数传递了一个lazy为true,会使得watch实例的dirty为true
    */
    // 这里我们先跳到后面看看 new Watcher
    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)  // 这里返回了 watcher 实例,保存在 `watchers = vm._computedWatchers` 中

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    // 组件正在定义的计算属性已经定义在现有组件的原型上则不会进行重复定义
    if (!(key in vm)) {    // 此时 key (`reversedMessage`) 不在 vm 实例上,所以就定义计算属性,来到下面的 `defineComputed`
      // 定义计算属性
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      // 如果计算属性与已定义的data或者props中的名称冲突则发出warning
      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)
      }
    }
  }
}

Watcher

class Watcher {
    constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    /*_watchers存放订阅者实例*/
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep  
      this.user = !!options.user
      this.lazy = !!options.lazy  // true
      this.sync = !!options.sync
    } 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 // for lazy watchers => true
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    /*把表达式expOrFn解析成getter*/
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn    // 这里就是开头的 `reversedMessage`对应的函数
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        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    // true ,所以为undefined
      ? undefined
      : this.get()
  }

get () {
    /*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/

    /* Dep.target = null
        const targetStack = []

        // 将watcher观察者实例设置给Dep.target,用以依赖收集。同时将该实例存入target栈中
        export function pushTarget(_target: Watcher) {
            // 这里如果 Dep.target 已经存在,就先暂存在栈中,先将 Dep.target 赋值为 此时的 watcher,
            // 到最后 popTarget 中再把之前的  Dep.target 给重新恢复
             if (Dep.target) targetStack.push(Dep.target)
             Dep.target = _target
        }

        // 将观察者实例从target栈中取出并设置给Dep.target
        export function popTarget() {
            Dep.target = targetStack.pop()
        } */    

    pushTarget(this)    // 现将全局的 Dep.target 设置为当前这个 计算属性对应的 watcher ,此时并不入栈

    let value
    const vm = this.vm

    // 这里执行了 getter 方法,也就是 reversedMessage 对应的 函数
    // 在执行的过程中会获取  this.message 的值,这里就会触发  this.message 的 `get` 方法,
    // 因为 data 里的每个属性都已被 observe 了,重写了[get/set](https://github.com/vuejs/vue/blob/v2.3.0/src/core/observer/index.js#L152)
    // 并且此时 Dep.target 是当前这个 计算属性对应的 watcher,所以  this.message 收集的依赖中就会有该 watcher 了,
    // 从而当 this.message 发生改变时,执行 set ,触发 dep.notify() 通知所有的观察者,
    // 然后执行 watcher 的 update 方法,这样 reversedMessage  就和 this.message  联系在了一起
    // 这里似乎看上去即使你 this.message 改变了 notify -> update 的时候因为 lazy = true,仅仅改变了dirty = true
   // 但是这里并没有联系上 compile 的时候,即 `<p>Reversed message: "{{ reversedMessage() }}"</p>` 的情况,
     // 所以 当改变 this.message 的时候是会触发试图的更新的,即该计算属性不仅仅只有这一个 watcher 

    if (this.user) {
      try {
        value = this.getter.call(vm, vm)
      } catch (e) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      }
    } else {
      value = this.getter.call(vm, vm)
    }
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    //如果存在deep,则触发每个深层对象的依赖,追踪其变化
    if (this.deep) {  // 此时为 false ,所以不触发这里
      //递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系
      traverse(value)
    }

    //将观察者实例从target栈中取出并设置给Dep.target
    popTarget()    // 此时Dep.target为null
    this.cleanupDeps()
    return value
  }

evaluate () {    // 当使用计算属性时,就会执行 `this.get()` 方法,
    this.value = this.get()  // 执行函数,并获取返回值
    this.dirty = false
  }

update () {
    /* istanbul ignore else */
    // 在计算属性时,因为各个计算属性都是lazy= true
    // 这里当重新访问计算属性时,createComputedGetter里面调用 watcher.evaluate() 会将 this.dirty = false
    // 这样,再次获取计算属性的时候,就会取之前的值,只有当计算属性依赖的data 值发生改变通知watcher 时
    // 才会将dirty 置为true,从而使再次访问计算属性时,会获取最新的值
    if (this.lazy) {    // 此时为 true
      this.dirty = true
    } else if (this.sync) {
      /*同步则执行run直接渲染视图*/
      this.run()
    } else {
      /*异步推送到观察者队列中,下一个tick时调用。*/
      queueWatcher(this)
    }
  }

   cleanupDeps () {
    /*移除所有观察者对象*/
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

defineComputed

定义计算属性

export function defineComputed(target: any, key: string, userDef: Object | Function) {
  if (typeof userDef === 'function') {  // 此时 userDef 对应的 reversedMessage 是一个函数,否则如果是对象就走下面的 else
    /*创建计算属性的getter*/
    sharedPropertyDefinition.get = createComputedGetter(key)   // 返回 computedGetter 函数
    /*
      当userDef是一个function的时候是不需要setter的,所以这边给它设置成了空函数。
      因为计算属性默认是一个function,只设置getter。
      当需要设置setter的时候,会将计算属性设置成一个对象。参考:https://cn.vuejs.org/v2/guide/computed.html#计算-setter
    */
    sharedPropertyDefinition.set = noop
  } else {
    /*get不存在则直接给空函数,如果存在则查看是否有缓存cache,没有依旧赋值get,有的话使用createComputedGetter创建*/
    sharedPropertyDefinition.get = userDef.get ?
      userDef.cache !== false ?
      createComputedGetter(key) :
      userDef.get :
      noop
    /*如果有设置set方法则直接使用,否则赋值空函数*/
    sharedPropertyDefinition.set = userDef.set ?
      userDef.set :
      noop
  }
  /*defineProperty上getter与setter*/
    // 将 计算属性绑定到 vm 实例上
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

createComputedGetter

创建计算属性的getter

function createComputedGetter(key) {
  return function computedGetter() {
    // 获取到 该计算属性对应的 watcher 实例
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      /*实际是脏检查,在计算属性中的依赖发生改变的时候dirty会变成true,在get的时候重新计算计算属性的输出值*/
      if (watcher.dirty) {   // 此时为true
        watcher.evaluate()    // 来到上面的 watcher
      }
      /*依赖收集*/
      if (Dep.target) {
        watcher.depend()
      }
    // 返回 watcher.value 也就是 reversedMessage 执行的返回值
      return watcher.value
    }
  }
}

参考

learnVue

上面仅仅是浅析了一部分简单情况下的 computed 代码,如有不当,欢迎指出 ^_^
Vue 很精彩,我们继续 ^_^