按应用实例化的流程,在学习完挂载渲染后,我们回过头来继续研究 Vue 对数据相关的初始化。在 _init 中是通过调用 initState(vm) 对数据相关的做初始化的,我们跟进去看看,它在文件 /src/core/instance/state.js 中
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props) // 初始化 props
if (opts.methods) initMethods(vm, opts.methods) // 初始化 methods
if (opts.data) {
initData(vm) // 初始化 data
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed) // 初始化 computed
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch) // 初始化 watch
}
}
数据对象的初始化(options.data)
这里分别对 options 选项里的 props、methods、data、computed 和 watch 做了初始化操作,这里我们先只看 data 的处理。对于没有 options.data 的情况直接调用了 observe 处理,对于有options.data 的情况执行 initData 也是调用 observe 处理数据,它就是对数据做响应式观察的关键函数。我们先看有 options.data 的情况,跟进去 initData,它与 initState 同文件。
function initData (vm: Component) {
// 禁用依赖收集的情况下获取 options.data 经过处理后的的值
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm) // 在合并选项章节定义处理 data 的函数
: 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 (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)) {
// 代理 this._data.xxx 到 this.xxx
// 在介绍 proxy 和 defineReactive 函数章节有详细说明
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
在 initData 中,对 options.data 中判断获取值,options.data 如果是函数则调用 getData 执行函数返回数据对象,把 options.data 转换为数据对象赋值给 vm._data。然后通过调用 proxy 函数把全部属性都从 vm._data 代理到 vm 上,这个在学习 proxy 函数时有详细讲解。然后把 vm._data 传参给 observe 函数,我们来详细看看这个函数,它在文件 /src/core/observer/index.js 中定义
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
// value 为 非对象 或者 Vnode 则退出
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 如果 value 中存在 __ob__ 属性则赋值给 ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 启用观察模式
// 非服务端渲染模式
// value 为数组或者对象
// value 可扩展
// value._isVue 为假(_isVue 为真即该对象是Vue组件实例,不用观察)
// 以上条件都符合的情况下把 value 做参数 实例化 Observer 对象
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++ // Vue 实例的根 data 则 ob.vmCount 加一
}
return ob
}
observe 这个函数也是响应式中的关键,它只处理对象数据类型,它的作用是判断对象中是否有 ob 属性,有则直接返回该属性值,没有则 new Observer 实例化一个类对象并返回该实例对象。正是在这个 Observer 类中,对这个对象做了一系列的响应式观察操作。
在 new Observer 时把数据对象传入构造函数,经过 Observer 实例化处理后过,数据对象就具备了响应式的属性,这个细节我们在后续继续深入研究。
总结:
对于数据的处理,先 proxy 代理到 vm 实列上,这样用户就可以在必要的地方直接通过 this.xxx 的方式引用。然后调用 observe 函数做响应式观察处理,可以简单的理解为传入 observe 函数处理的对象,都具备了响应式的能力,对数据对象的属性的引用 this.xxx 与 修改 this.xxx = yyy 都会触发某些处理,这个我们后续章节继续研究学习。
在 initState 中初始化了很多 options 属性,这里我们先单独关注数据对象的处理,这里我们做大概流程的学习,后面会继续深入研究学习其过程。