乐趣区

Vue初始化中的选项合并两种策略

在 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 做配置合并。

退出移动版