本文分析的 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 方法主要是对 props、methods、data、computed 和 wathcer 等属性做了初始化操作,接下来我详细分析一下 initProps 和 initMethods 和 initData 过程,initComputed 和 initWatch我打算单独来讲,具体分析我放在了 《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 的初始化过程:
- 通过
defineReactive方法把props的每一个值变成响应式 - 通过
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.defineProperty 把 props 和 data 上的属性代理到 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的初始化过程:
- 将
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初始化过程:
- 将
data函数返回的对象遍历,通过proxy把每一个值vm._data.xxx都代理到vm.xxx上 - 调用
observe方法观测整个data的变化,把data也变成响应式
在这里我们详细不探讨 observe 方法,详细分析请看 《Vue.js 源码分析三:深入响应式原理》。
总结
initState 方法主要是对 props、methods、data、computed 和 wathcer 等属性做了初始化操作,并代理到 vm实例上。