Vue2.x源码学习笔记(二)——initState

272 阅读3分钟

这里初始化了props,methods,data,computed以及watch。

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 */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

这里的_watchers用于存放watch:{}中的watcher,以及用户通过$watch创建的watcher。然后获取$options上的props,methods,data,computed,watch来进行初始化。

initProps

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  if (!isRoot) {
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    defineReactive(props, key, value)
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

这里的propsData是父组件或用户提供的props数据。而$options.prop是用户在子组件内定义的props:{}。循环$options.props,将props的每一个key缓存在vm.$options._propKeys上来优化性能:将来更新props时只需要遍历vm.$options._propKeys数组即可得到所有props的key。如果当前实例不是根组件,即isRoot是false,则不用对props的值进行响应式处理。这里通过toggleObserving方法将变量shouldObserve置为false。在observe方法中对于shouldObserve为false时不会给值添加响应式标记__ob__。

validateProp

 function validateProp (
  key: string,
  propOptions: Object,
  propsData: Object,
  vm?: Component
): any {
  const prop = propOptions[key]
  const absent = !hasOwn(propsData, key)
  let value = propsData[key]
  const booleanIndex = getTypeIndex(Boolean, prop.type)
  if (booleanIndex > -1) {
    if (absent && !hasOwn(prop, 'default')) {
      value = false
    } else if (value === '' || value === hyphenate(key)) {
      const stringIndex = getTypeIndex(String, prop.type)
      if (stringIndex < 0 || booleanIndex < stringIndex) {
        value = true
      }
    }
  }
  if (value === undefined) {
    value = getPropDefaultValue(vm, prop, key)
    const prevShouldObserve = shouldObserve
    toggleObserving(true)
    observe(value)
    toggleObserving(prevShouldObserve)
  }
  if (
    process.env.NODE_ENV !== 'production' &&
    !(__WEEX__ && isObject(value) && ('@binding' in value))
  ) {
    assertProp(prop, key, value, vm, absent)
  }
  return value
}

通过validateProp获取prop的值:这里对prop是Boolean类型的进行了特殊处理。当在父组件中没有给子组件传递对应的prop数据,并且子组件没有给prop设置default默认值时那么value就是false。如果在父组件上传了值为空字符串,或者值与key相等,比如这几中情况:

<child userName=""></child>
<child userName></child>
<child userName="user-name"></child>
<child name="name"></child>

user-name会被hyphenate转为驼峰。这几种情况value都是true。

如果在子组件中的props中定义了,但父组件中没有传递进来。那么通过getPropDefaultValue去获取默认值:没定义default时value就是undefined。如果有default则value就是default。对于array和object类型default会传入一个函数。那就调用函数获取default。然后通observe(value)响应式侦测这个默认值。

最后通过defineReactive将props添加到vm._props上。然后通过proxy将_props代理到实例上。通过this.xxx就能获取props。

initMethods

function initMethods (vm: Component, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      if (typeof methods[key] !== 'function') {
        //error
      }
      if (props && hasOwn(props, key)) {
       //error
      }
      if ((key in vm) && isReserved(key)) {
        //error
      }
    }
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

这里会校验用户提供的methods中每一项是否是函数,名称是否与props上的重名。校验methos中的key与Vue实例上已有的方法是否重叠,一般是一些内置方法,比如以 $ 和_开头的方法。最后将方法绑定到实例上。可以通过this.xxx()调用。

initData

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  observe(data, true /* asRootData */)
}

首先判断用户提供的data是函数还是对象,如果是函数则通过getData去获取data并赋值给vm._data。然后校验data中的key是否与methods和props重名。然后将_data代理到实例上。最后通过observe去观测,使data响应式。

initComputed

const computedWatcherOptions = { lazy: true }
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) {
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }
    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)
      }
    }
  }
}

如果计算属性的值是一个函数就将函数赋值给getter,如果是一个对象,就获取它的get函数。然后创建一个watcher,watcher的option中有lazy属性,同时将这个watcher存放在vm._computedWatchers中。如果不与data,props,或实例中的其他属性重名,就调用defineComputed。

defineComputed

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : 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)
}

首先构造属性描述符(get、set),对计算属性值时函数还是对象进行特殊处理,对于get进行处理成createComputedGetter。

createComputedGetter

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
    }
  }
}

首先获取之前保存的watcher。因为之前创建watcher时的option中lazy为true,所以Watcher内部的dirty属性也为true,因此首次会调用watcher.evaluate(),evaluate函数中会调用计算属性的get去获取值,并将dirty置为false。并将值保存在watcher的value上。计算属性就是获取的这个value。因此第二次执行时因为dirty是false所以不会再调用get方法。待页面更新后,wathcer.update方法会将watcher.dirty重新置为 true。

随后判断Dep.target是否存在,如果存在,则调用watcher.depend方法。这段代码的目的在于将读取计算属性的那个Watcher添加到计算属性所依赖的所有状态的依赖列表中。就是让读取计算属性的那个Watcher持续观察计算属性所依赖的状态的变化。

initWatch

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

根据用户提供的watch通过createWatcher去创建watcher,如果是函数数组,则循环调用createWatcher,最终去调用$watch方法。

proxy

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

这里通过Object.defineProperty来将key代理到target上。