当通过 import Vue from ‘vue’方式来引入vue,此时在new 之前 ,Vue的原型上已经挂载了许多初始化的属性及方法,通过
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
当我们new Vue时,会执行以下Vue函数,里边_init()方法,是通过initMixin函数定义的,继续往下看:
( 以下所有代码在开头都会有一个路径 )
// src -> core -> instance -> index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
// 初始化Vue
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原型上定义的
}
// 在import Vue from ‘vue’ 时,会执行以下函数
initMixin(Vue) // 函数执行时,定义了Vue原型上会定义_init 方法
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
initMixin函数:
在initMixin函数中,Vue原型上定义了_init 方法,其中的多数代码都去掉了,merge options注释 ,这个是在初始化时会把el / data / props / methods 上边的所有属性都合并到 实例的 $options 上去 ( vm.$options ),如何合并的会在以后文章中讲到。
继续往下,又会看到一系列初始化的函数, 本篇文章主要介绍下 initState(vm)中的 initData(vm)
// src -> core -> instance -> init.js
let uid = 0
export function initMixin (Vue: Class) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
··· ···
// a flag to avoid this being observed
··· ···
// merge options
··· ···
// expose real self
vm._self = vm
initLifecycle(vm) // 初始化钩子函数
initEvents(vm) // 事件
initRender(vm) // render函数
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) // 为data中的数据添加getter setter,及数据的响应式/监听
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
··· ···
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
initState函数:
在initState函数中,分别初始化了props / methods / data , 以及监听data中的数据, 我们来看下 initData( vm )函数:
// 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)
}
}
initData函数:
真正的data初始化是这个函数,首先会从实例的$options中获取到data,判断data是函数还是对象,因为data可以是一个函数(组件),也可以是一个对象(根实例),如果是函数会执行getData方法,getData方法简单来说就是返回了data函数执行的结果(是一个对象)赋值给 data和 vm._data;如果是对象直接把data赋值给data和 vm._data;如果data不存在,把空对象赋值给 data和 vm._data;(vm._data 就是data中的数据对象)
继续,isPlainObject(data)这个函数判断条件是:data是一个function,但是返回的结果不是一个对象,会抛出警告!
接下来data中的key会组成一个数组,遍历每个key与methods / props 比对,如果一样,会抛出异常,也就是说:data对象中定义的key(键)不能与props 或 methods 定义的key(键)一样!
继续,isReserved(key)函数判断key(键) 不可以 以_ 或 $ 符号开头,在文档中给出了原因:
继续,proxy(vm, `_data`, key) 这个函数是关键,为data每个数据添加getter setter,同时也能看到为什么通过this(vue实例)可以访问到data中的数据
// src -> core -> instance -> state.js
// 初始化data
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function' // vm._data 就是return 返回的对象
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) { // 判断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) // [key1, key2, key3, ··· ···]
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)) { // 判断data中的key 是否与props和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) // 为data每个数据添加getter setter
}
}
// observe data
observe(data, true /* asRootData */)
}
proxy函数:
其实proxy函数很简单,通过 Object.defineProperty(target, key, sharedPropertyDefinition),会把data中的数据代理转发到vue实例上了。
当我们通过vm.xxx 访问data中的数据时,实际上访问的是: this[sourceKey][key] , 也就是 vm. _data.[key] ,而vm._data 在上边提到过,它正是data中的数据对象,所以,当我们通过 实例 访问data中的数据时,访问的是 this._data.xxx
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
// proxy(vm, `_props`, key)
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)
}