写在前面
Vue构造函数的创建过程 一文中介绍了 Vue构造函数 的创建过程,其中第一个对 Vue构造函数 进行成员添加的就是 initMixin(Vue),该调用内创建了 _init 方法。可以说,Vue实例 的大门就是 _init 方法,因此,我们从这个方法入手,一步一步剖析 vm 是如何生成的。
/*
<div id="app">
<div>
<h1>{
{msg}}</h1>
<div id="extendUse"></div>
<child-component :msg="msg" />
</div>
</div>
*/
// Vue.extend 手动挂载组件
const ExtendUse = Vue.extend({
props: ['msg'],
template: `<h2>ExtendUse said: {
{msg}}</h2>`,
})
const ChildExtend = new ExtendUse({
propsData: {
msg: 'hello Extend'
}
}).$mount('#extendUse')
// Vue.component 自动挂载组件
const ChildComponent = Vue.extend({
props: ['msg'],
template: `<h2>child's father said: {
{msg}}</h2>`,
})
Vue.component('child-component', ChildComponent)
// Vue 实例
const vm = new Vue({
el: '#app',
data: {
msg: 'hello Vue',
}
})
按照惯例,我先将 _init 的源码简写一下,并且划分一下步骤:
Vue.prototype._init = function (options) {
// 步骤 - 1
const vm = this
vm._uid = uid++
vm._isVue = true
// 步骤 - 2
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
)
}
// 步骤 - 3
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
vm._self = vm
// 步骤 - 4
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created')
// 步骤 - 5
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
步骤 - 1
在这段代码中,我们首要了解的是 vm 指向了谁?
从它的调用 function Vue(options) { this._init(options) }
中我们可以了解到,Vue 是一个构造函数,而 _init 方法又不是该构造函数的静态方法,因此 this 指向了实例,所以 const vm = this
这一步其实就是将实例本身赋值给了 vm。
再具体点?const app = new Vue(options)
这里的 app 就是 vm 的值。
接着,在实例身上添加上 _uid 和 _isVue 的标识,记录当前实例是第 (_uid + 1) 个 Vue 实例对象。
步骤 - 2
这段代码是一个条件判断,判断的是是否是一个组件。那怎样的存在算是一个组件呢?Vue.extend 手动挂载上去的算不算一个组件呢?
我就先兜个底,只有存在于 vm.options.components 中的才算组件,且能进入 if 判断之中。这就意味着,要么 Vue.component(id, definition)
定义的对象,要么 new Vue({ components: { id: definition } })
定义的对象,其他方式都不具备 _isComponent 属性。因此,即使是 Vue.component 方法内同样也调用了的 Vue.extend 方法,在手动挂载时也不算作组件,不具备 _isComponent 属性。
那这段代码的意义是什么呢?简而言之,就是将 Vue构造函数 中的成员变量 options 和我们传入的 options 合并然后挂载到实例的 $options 属性上。
initInternalComponent
不知道大家想没想过,组件为什么会进到 _init 中来,哪儿定义了它也可以进来进行初始化操作?
如果你没有忘记 详解Vue.component和Vue.extend 一文中讲过,Vue.component 中会调用一次 Vue.extend,将生成的实例存入 vm.options.components 之中。而在 Vue.extend 中我们曾定义过一个 VueComponent构造函数,这个构造函数继承了 Vue构造函数,因此也有 _init 方法,在 VueComponent构造函数 的 constructor 中又恰好调用了 this._init(options)
,所以套了一层又一层,剥丝抽茧后你应该就能明白为什么组件可以进来了吧。
那么,这个方法内做了些什么?当你进入函数体你会发现,options 中哪来的这么多属性???我丢,见都没见过啊,一脸懵逼逐渐变成N脸懵逼。所以我们先按下不表,因为其中涉及到模板编译部分,扯得太远回不来就麻烦了。