一. 前言
本文主要分析new Vue()
主要发生了什么,重点分析下new Vue(options)
中对象的的data
属性。以及vue
对data
里面的数据所作的this
绑定。然后仿照vue
的思路对一个构造函数实现类似的功能。
二. new Vue(options)
Vue
构造函数定义在src/core/instance/index.js
文件下
// 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'
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)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
可以看到在构造函数Vue
中对Vue
的使用方式进行限制,智能通过关键字new
来调用而不能当作普通函数来调用。然后调用_init
方法进行初始化。
三._init
方法
_init
方法是通过initMixin
方法挂载到Vue.prototype
上,initMixin
在src/core/instance/index.js
文件中定义。
// src/core/instance/init.js-> _init
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
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')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
_init
方法中可以看到初始化时只要作了以下工作
initLifecycle(vm)
初始化生命周期initEvents(vm)
初始化事件initRender(vm)
初始化渲染callHook(vm, 'beforeCreate')
调用钩子函数initInjections(vm)
在处理data
和props
前处理相关注入initState(vm)
初始化data
和props
computed
watcher
initProvide(vm)
callHook(vm, 'created')
调用钩子函数
在init方法中我们可以看到进行了
el
的判断,如果el
存在,那么会调用$mount
方法进行挂载,如果没有el
就不会调用$mount
进行挂载,所以在使用vue
时有如下两种方式进行挂载
new Vue({el:"#root"})
new Vue(options).$mount('#root')
四. initState
在vue
中通过this.valname
拿到data
中定义的变量值,通过分析initState
方法中的initData
方法来看看vue
背后是如何实现的.
// src/core/instance/state.js-> initState
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) // 分析initData
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
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)
}
}
// observe data
observe(data, true /* asRootData */)
}
在initData
方法中会通过vm.$options.data
拿到options
中定义的data
,然后 调用getData
(如果data
是函数) 获取定义好的数据并赋值给vm._data
。通过object.keys
遍历获取data
中所有属性的键值keys
,然后判断props
或者methods
对象中是否存在同名属性,有的话就给出错误提示。
遍历keys
,调用proxy
方法进行代理.
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)
}
proxy
方法主要是调用Object.defineProperty
方法给当前vm
添加data
中的每个属性,属性值是get
和set
方法。
new Vue({
data() {
return {
msg: 'hello world'
}
},
methods: {
getMsg() {
return this.msg
}
setMsg(msg) {
this.msg = msg
}
}
})
当我们在getMsg
方法中访问 this.msg
会触发msg
的get
方法,这是会返this['_data']['msg']
。当在setMsg('hello vue')
方法修改msg
的值时,会触发当前实例的msg
属性的set
方法,重新设置msg
的值。也就是this['_data']['msg'] = 'hello vue'
。
这是
vue
帮我们在背后所作的工作之一,将data
中的数据绑定在this
上,可以避免我们通过this._data.msg
来获取data
中定义的msg
值。
五.小结
本文主要介绍了new Vue()
发生了哪些事情,主要是调用_init
方法进行各种初始化操作。
然后重点分析了vue
将data
中所有的属性通过 Object.defineProperty
方法代理到当前this
实例上,方便对data
中属性的访问。这也是设计模式中代理模式的一种体现,我们直接访问data
中的属性不方便或者通过this._data
进行访问不合理不规范(_data默认为私有变量外部无法访问或者尽量不访问
)。