在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就比较复杂,我们来一步步拆解一下:

  1. new Vue时,根实在this._init函数中执行例执行$mount方法
  2. $mount获取到temple模板后,通过compileToFunctions函数获取render函数,处理完后执行真正的mount挂载函数。
  3. mount的本质其实是执行了mountComponet方法
  4. mountComponet中主要做了两件事,定义了updateComponent方法,创建组件对应的watcher。而updateComponent作为参数传给Watch,Watcher的构造函数中,在执行get方法时会调用他。
  5. updateComponent函数本质是执行vm实例上的_update方法,他是通过一个patch函数,来计算出新旧vnode节点的差异,并执行对应的更新操作
  6. 因为初始化时,没有旧的节点,因此直接根据新的vnode,直接执行创建dom元素的createElm函数,如果发现这个vnode是个自定义组件,则执行createComponent函数
  7. 因为自定义组件不是<div>标签,能够直接生成dom树,他需要创建一个组件实例,像根实例一样,在这个组件实例的基础上执行mount方法,向下递归直到该组件中所有vonde都能直接转为dom,这样的结构其实就是Vue教程中讲的组件的组织
  8. 在创建组件实例的createComponentInstanceForVnode方法中,我们最终找到了这个_isComponent属性,当他去创建组件实例(VueComponent)的时候,就会又执行我们熟悉的_init方法,这时候我们的option里就有_isComponent属性了。因此,initInternalComponent是创建组件实例时执行的策略

从上面的分析可知,这两种合并策略一种是为Component组件的情况下,在从vnode创建组件实例是,会执行initInternalComponent进行内部组件配置合并;一种是非组件的情况,即根实例创建时,直接通过mergeOptions做配置合并。