3

在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里分别打个断点,看看他执行的逻辑。
image.png
当我们重新刷新页面,发现首先进入的逻辑是else里的mergeOptions:
image.png
而从它的调用栈call Stack中可以回溯到真正来源是一个匿名函数,而这个匿名函数其实就是new Vue:
image.png
因此,mergeOption其实是在根实例初始化时执行的策略。

image.png
当断点进入initInternalComponent中时,它的调用栈call Stack就比较复杂,我们来一步步拆解一下:

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

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


FrankChencc
214 声望9 粉丝