new Vue() 时做了什么
import Vue from 'vue'
var app = new Vue({
el: '#app',
mounted() {
console.log(this.message) //hello
},
data() {
return {
message: 'hello'
}
}
})
这块代码我们很熟悉,引用vue,然后创建Vue实例,那我们有没有想过我们在data
中定义的key
是如何挂载到实例上的,比如我们在mounted
的钩子函数中是如何通过this.message
获取到我们定义的值。
通过分析这部分代码,我想向大家分享阅读源码的心得:最好有一个明确的目的,例如分析data
的挂载,然后在分析的过程中可以暂时跳过一些不相关的代码,没必要一下子深扒所有代码。
src/core/instance/index.js
function Vue (options) {
...
this._init(options)
}
initMixin(Vue)
首先从instance
目录的入口开始,我们看到了Vue的构造函数,在构造函数中看到vue在实例化时执行了_init
方法,而这个方法则是通过调用initMixin
方法将_init
方法定义在Vue的原型上。
src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
...
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm) //@1
initProvide(vm)
callHook(vm, 'created')
...
}
}
@1:
_init
方法中主要做了一些初始化的操作,这里我们先略过方法中的其他代码,主要看 initState(vm)
做了什么,它跟挂载data
相关。
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) //@1
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
@1:
initState
方法中主要是将options
中的data
, props
, methods
等挂载到vm
实例上,这里我们看一下initData(vm)
的代码:
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 (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) //@1
}
}
// observe data
observe(data, true /* asRootData */)
}
@1:
核心代码就是
proxy(vm, `_data`, key)
vue通过proxy方法将key代理到vm的_data上,我们看详细的代码实现:
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) //@1
}
参数target
就是传入的vm实例,sourceKey
就是代理的key:_data
,key
则是我们要访问的键。
@1:
定义实例对象的属性描述,添加了get
和set
方法,当我们访问this.key
的时候,实际上是返回了this._data.key
,我们可以在一开始的代码中打印this._data.message
,也可以返回相同的内容。