かかってこいよ NakamuraEmi
  1. 1 かかってこいよ NakamuraEmi
  2. 2 The Night We Stood Lyn
  3. 3 Life Will Change Lyn
  4. 4 Warcry mpi
  5. 5 Time Bomb Veela
  6. 6 Hypocrite Nush
  7. 7 Libertus Chen-U
  8. 8 Flower Of Life 发热巫女
  9. 9 Last Surprise Lyn
  10. 10 One Last You Jen Bird
  11. 11 Quiet Storm Lyn
2017-12-03 12:27:51

Vue原理解析——依赖收集

上篇文章中我们谈到了数据的响应化的工作流程,其中很重要的一部分就是依赖收集的过程。实际上依赖收集不只在数据响应化时用到,这篇文章就集中来谈谈。


我们之前说过数据的响应化是从initState这个函数中的initData函数开始的,实际上initData只是该函数的一部分,该函数内部实现如下:

export function initState (vm: Component) {
  // 首先在vm上初始化一个_watchers数组,缓存这个vm上的所有watcher
  vm._watchers = []
  // 获取options,包括在new Vue传入的,同时还包括了Vue所继承的options
  const opts = vm.$options
  // 初始化props属性
  if (opts.props) initProps(vm, opts.props)
  // 初始化methods属性
  if (opts.methods) initMethods(vm, opts.methods)
  // 初始化data属性
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // 初始化computed属性
  if (opts.computed) initComputed(vm, opts.computed)
  // 初始化watch属性
  if (opts.watch) initWatch(vm, opts.watch)
}

可以看到除了data属性的数据初始化,还存在着props,computed,watch属性的初始化,这些属性也正是同样需要依赖收集的地方。

initProps

实例化Vue对象时,会通过props属性表示从父组件传递的值。对该值的处理都集中在initProp函数中,具体如下:

function initProps (vm: Component, propsOptions: Object) {
  // propsData主要是为了方便测试使用
  const propsData = vm.$options.propsData || {}
  // 新建vm._props对象,可以通过app实例去访问
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  // 缓存的prop key
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  observerState.shouldConvert = isRoot
  for (const key in propsOptions) {
    // this._init传入的options中的props属性
    keys.push(key)
    // 注意这个validateProp方法,不仅完成了prop属性类型验证的,同时将prop的值都转化为了getter/setter,并返回一个observer
    const value = validateProp(key, propsOptions, propsData, vm)

    // 将这个key对应的值转化为getter/setter
      defineReactive(props, key, value)
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    // 如果在vm这个实例上没有key属性,那么就通过proxy转化为proxyGetter/proxySetter, 并挂载到vm实例上,可以通过app._props[key]这种形式去访问
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  observerState.shouldConvert = true
}

可以看到该函数中主要做的事情有两个,一是验证props,二是我们之前提到的数据响应式处理。

先看验证props的validateProp函数,该函数实现如下:

function validateProp (
  key: string,
  propOptions: Object,    // $options.props属性
  propsData: Object,      // $options.propsData属性
  vm?: Component
): any {
  const prop = propOptions[key]
  // 如果在propsData测试props上没有缓存的key
  const absent = !hasOwn(propsData, key)
  let value = propsData[key]
  // 处理boolean类型的数据
  // handle boolean props
  if (isType(Boolean, prop.type)) {
    if (absent && !hasOwn(prop, 'default')) {
      value = false
    } else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) {
      value = true
    }
  }
  // check default value
  if (value === undefined) {
    // default属性值,是基本类型还是function
    // getPropsDefaultValue见下面第一段代码
    value = getPropDefaultValue(vm, prop, key)
    // since the default value is a fresh copy,
    // make sure to observe it.
    const prevShouldConvert = observerState.shouldConvert
    observerState.shouldConvert = true
    // 将value的所有属性转化为getter/setter形式
    // 并添加value的依赖
    observe(value)
    observerState.shouldConvert = prevShouldConvert
  }
  if (process.env.NODE_ENV !== 'production') {
    assertProp(prop, key, value, vm, absent)
  }
  return value
}

这部分主要任务在于props的默认值获取,然后通过observe函数将其响应化。之后的实现就和initData中的依赖收集相同了。大致流程还是触发订阅者函数 => 订阅者放入Dep.target => 订阅者触发prop的getter => getter将订阅者放入这个prop的订阅者数组中 => 依赖收集完成 => prop更新时触发订阅者的更新。

initComputed

计算属性初始化的过程中,会为每个属性先实例化一个订阅者,代码如下:

const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {
  // 新建_computedWatchers属性
  const watchers = vm._computedWatchers = Object.create(null)

  for (const key in computed) {
    const userDef = computed[key]
    // 如果computed为funtion,即取这个function为getter函数
    // 如果computed为非function.则可以单独为这个属性定义getter/setter属性
    let getter = typeof userDef === 'function' ? userDef : userDef.get
    // create internal watcher for the computed property.
    // lazy属性为true
    // 注意这个地方传入的getter参数
    // 实例化的过程当中不去完成依赖的收集工作
    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)

    // 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)) {
      defineComputed(vm, key, userDef)
    } 
  }
}

由于传入了{lazy: true}的配置选项,组件初始化时不会立即对计算属性进行求值和依赖收集,而是实例化为订阅者后同时将其响应化。当访问该计算属性时再触发相同的依赖收集流程。需要的是计算属性响应化后的getter会新增一条对其订阅者的dirty属性的判断。代码如下:

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 是否需要重新计算
      if (watcher.dirty) {
        watcher.evaluate()
      }
      // 管理依赖
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

该属性主要用于判断该计算属性是否需要根据其依赖重新求值,否则就使用缓存值。

initWatch

这个函数就比较简单了,watch选项或是$watch方法都能触发,就是实例化一个订阅者将其放入观测属性的订阅者数组中即可。走的流程仍然是依赖收集,区别在于这里的依赖是我们手动定义的key。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「Vue」 标签。