Vue.js 源码分析二:initState 原理

781 阅读2分钟

本文分析的 Vue 源码版本是 2.6.10

如果还不熟悉 Vue 实例化过程的,可以看之前写的一篇文章《Vue.js 源码分析一:Vue 实例化过程》

在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) 方法。

initState

src/core/instance/state.js中:

export function initState (vm: Component) {
  // 初始化 _watchers 属性
  vm._watchers = []
  const opts = vm.$options
  // 初始化 props
  if (opts.props) initProps(vm, opts.props)
  // 初始化 methods
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    // 初始化 data
    initData(vm)
  } else {
    // 如果配置中没有data默认设置一个空对象,并作为根data
    observe(vm._data = {}, true /* asRootData */)
  }
  // 初始化 computed
  if (opts.computed) initComputed(vm, opts.computed)
  // 初始化 watch
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

我们可以看到 initState 方法主要是对 propsmethodsdatacomputedwathcer 等属性做了初始化操作,接下来我详细分析一下 initPropsinitMethodsinitData 过程,initComputedinitWatch我打算单独来讲,具体分析我放在了 《Vue.js 源码分析四:计算属性和侦听器》

initProps

src/core/instance/state.js中:

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
     	...
    } else {
      // props 每个 key 的值设置为响应式
      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.
    if (!(key in vm)) {
      // 把 vm._props.xxx 的访问代理到 vm.xxx 上
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

可以看到props 的初始化过程:

  1. 通过 defineReactive方法把 props 的每一个值变成响应式
  2. 通过 proxy 把每一个值 vm._props.xxx 的访问代理到 vm.xxx

在这里我们详细不探讨 definedReactive 方法,详细分析请看 《Vue.js 源码分析三:深入响应式原理》

  • Proxy
var sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};

function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

代理的作用是利用 Object.definePropertypropsdata 上的属性代理到 vm 实例上

initMethods

src/core/instance/state.js中:

function initMethods (vm: Component, methods: Object) {
  // 拿到 props 属性
  const props = vm.$options.props
  for (const key in methods) {
    ...
    // methods 中的方法挂载到 vm 实例上,通过this.methods[key]访问
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

可以看到methods的初始化过程:

  1. methods中的方法挂载到vm实例上

initData

src/core/instance/state.js中:

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // 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 (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
  observe(data, true /* asRootData */)
}

可以看到data初始化过程:

  1. data函数返回的对象遍历,通过proxy把每一个值vm._data.xxx都代理到vm.xxx
  2. 调用observe方法观测整个data的变化,把data也变成响应式

在这里我们详细不探讨 observe 方法,详细分析请看 《Vue.js 源码分析三:深入响应式原理》

总结

initState 方法主要是对 propsmethodsdatacomputedwathcer 等属性做了初始化操作,并代理到 vm实例上。