在 Vue 的 _init
方法中,合并选项是通过下面的代码实现的
// 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
)
}
从上面 if else
能看出来,合并策略有两个,当选项存在并且 _isComponent
为 true 时,执行的是 initInternalComponent
,否则执行mergeOptions
。那么什么情况下_isComponent
会为 true 呢?
两种策略
我们用一个简单的例子来分析一下这两个策略执行的时机。
<div id="demo">
<comp :msg="msg" @log-msg="logMsg"></comp>
</div>
<script>
Vue.component('comp', {props: ['msg'],
template: `
<div class="blog-post">
<h3>{{msg}}</h3>
<button @click="$emit('log-msg', msg)">
Console Message
</button>
`
});
// 创建实例
const app = new Vue({
el: '#demo',
data: {msg: 'props-message'},
methods: {logMsg(data) {console.log(data)
}
}
});
</script>
首先,我们分别在选项合并的 if 和 else 里分别打个断点,看看他执行的逻辑。
当我们重新刷新页面,发现首先进入的逻辑是 else 里的 mergeOptions:
而从它的调用栈 call Stack
中可以回溯到真正来源是一个匿名函数,而这个匿名函数其实就是 new Vue:
因此,mergeOption 其实是在根实例初始化时执行的策略。
当断点进入 initInternalComponent
中时,它的调用栈 call Stack
就比较复杂,我们来一步步拆解一下:
-
new Vue
时,根实在this._init
函数中执行例执行 $mount 方法 - $mount 获取到 temple 模板后,通过
compileToFunctions
函数获取 render 函数,处理完后执行真正的 mount 挂载函数。 - mount 的本质其实是执行了 mountComponet 方法
- mountComponet 中主要做了两件事,定义了 updateComponent 方法,创建组件对应的 watcher。而 updateComponent 作为参数传给 Watch,Watcher 的构造函数中,在执行 get 方法时会调用他。
- updateComponent 函数本质是执行 vm 实例上的_update 方法,他是通过一个 patch 函数,来计算出新旧 vnode 节点的差异,并执行对应的更新操作
- 因为初始化时,没有旧的节点,因此直接根据新的 vnode,直接执行创建 dom 元素的 createElm 函数,如果发现这个 vnode 是个自定义组件,则执行 createComponent 函数
- 因为自定义组件不是 <div> 标签,能够直接生成 dom 树,他需要创建一个组件实例,像根实例一样,在这个组件实例的基础上执行 mount 方法,向下递归直到该组件中所有 vonde 都能直接转为 dom,这样的结构其实就是 Vue 教程中讲的 组件的组织
- 在创建组件实例的
createComponentInstanceForVnode
方法中,我们最终找到了这个_isComponent
属性,当他去创建组件实例(VueComponent)的时候,就会又执行我们熟悉的_init
方法,这时候我们的 option 里就有_isComponent
属性了。因此,initInternalComponent 是创建组件实例时执行的策略
从上面的分析可知,这两种合并策略一种是为 Component
组件的情况下,在从 vnode 创建组件实例是,会执行 initInternalComponent
进行内部组件配置合并;一种是非组件的情况,即根实例创建时,直接通过 mergeOptions
做配置合并。