我们在Vue对象中可以通过this.xxx
去访问Vue中data
对象中定义的数据,但是为什么可以直接使用this.xxx
来访问呢?我们可以用过看源码的方式来进行理解。
当我们使用new的方式创建一个Vue实例的时候,实际上调用的是一个Vue的方法。
/* src/core/instance/index.js */
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
初始化的时候,Vue方法中主要调用了_init
这个方法。
这个_init
方法实际上是一个挂在在Vue原型上的一个方法。同样在这个文件中含有一行代码initMixin(Vue)
就是完成了挂载_init
方法在Vue原型上的一个操作。
然后我们进入_init(options)
来看进行了哪些操作。这个时候进入src/core/instance/init.js找到initMixin
方法。
在initMixin
一开始就可以看到Vue.prototype._init = function() {}
这个就是Vue对象原型上的_init
方法了。然后来看这个方法中进行了哪些操作?这个时候忽略一些细节,直接看到有一段全是initXXX的函数调用。这一块大致就是Vue对象初始化的一些流程,比如生命周期,事件等等。
/* src/core/instance/init.js */
export function initMixin (Vue: Class) {
Vue.prototype._init = function (options?: Object) {
/* more details */
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* more details */
}
}
关于data的处理就聚焦在这个initState
方法上。initState
这个方法是引入到这个文件的,所以我们找到initState
这个方法真正定义的地方src/core/instance/state.js。
initState
定义的结构看起来很简单,就是根据我们的options上的不同属性来做相应的初始化处理。
/* src/core/instance/state.js */
export 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)
}
}
我们的主要目标还是data,这个时候我们我们就主要去看data属性下进行的操作。data选项存在时候就调用了initData
,不存在的时候就调用了observer
,同时把data用一个空对象赋值。observer
和数据响应式相关,这里先不详细说明。
接下来就是去看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' +
'',
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)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
initData
的操作分为三个部分:
- 判断data是否是一个函数
- 数据代理
- 响应式(暂不详细说明)
我们在创建一个新的Vue对象的时候,data是一个函数,返回一个含有定义参数的新对象,如果不是一个函数,就会进行一个报错。具体原因和组件相关。
new Vue({ data() { return { message: 'Hello world', } }, }); new Vue({ data: { message: 'Hello world', }, });
数据代理的时候要确保data中的属性名称不能和
props
、method
和保留属性不能重名,因为最后这些都会挂载在Vue的实例上。代理的实现部分依赖proxy
这个方法。/* src/core/instance/state.js */ 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) }
通过设置一个对象上的访问器属性的
get
和set
,就能使我们在访问vm.xxx
的时候去访问vm._data.xxx
了。vm
的_data
属性则是在initData
中判断data是否使一个函数的时候的赋值操作。这样当我们去访问vm.xxx
实质上就是访问的我们定义的data上的属性了。