合并配置 new Vue 有两个场景,一种是外部代码主动主动调用 new Vue(options),另一种是组件内部通过 new Vue(options)实例化组件,无论哪种场景,都会用到_init(options),首先会执行 merge options 的操作,组件内部通过 new Vue(options)实例化比外部主动调用 new Vue(options)要快,合并完的结果保存在 vm.$…

合并配置

new Vue 有两个场景,一种是外部代码主动主动调用 new Vue(options),另一种是组件内部通过 new Vue(options)实例化组件,无论哪种场景,都会用到_init(options),首先会执行 merge options 的操作,组件内部通过 new Vue(options)实例化比外部主动调用 new Vue(options)要快,合并完的结果保存在 vm.$options 中

外部调用场景下的合并配置是通过 mergeOption,遵循一定的合并策略,对于不同的 key 的合并策略是不一样

通过 initInternalComponent 进行 merge,在子组件构造函数,先把 Vue.options 和子组件的 option 合并,然后在实例化子组件的时候,再把合并的 options,Sub.options 和传入 options 进行合并

  • 以此为例
import Vue from 'vue';

let childComp = {
  template: '<div>{{msg}}</div>',
  created() {
    console.log('child created');
  },
  mounted() {
    console.log('child mounted');
  },
  data() {
    return {
      msg: 'Hello Vue'
    };
  }
};

Vue.mixin({
  created() {
    console.log('parent created');
  }
});

let app = new Vue({
  el: '#app',
  render: (h) => h(childComp)
});

// 外部调用 new Vue后 options的merge 合并策略后的结构
vm.$options = {
  components: {},
  created: [
    function created() {
      console.log('parent created');
    }
  ],
  directives: {},
  filters: {},
  _base: function Vue(options) {
    // ...
  },
  el: '#app',
  render: function(h) {
    //...
  }
};
// 组件调用场景 new Vue后 options的merge 合并策略后的结构
vm.$options = {
  parent: Vue /*父Vue实例*/,
  propsData: undefined,
  _componentTag: undefined,
  _parentVnode: VNode /*父VNode实例*/,
  _renderChildren: undefined,
  __proto__: {
    components: {},
    directives: {},
    filters: {},
    _base: function Vue(options) {
      //...
    },
    _Ctor: {},
    created: [
      function created() {
        console.log('parent created');
      },
      function created() {
        console.log('child created');
      }
    ],
    mounted: [
      function mounted() {
        console.log('child mounted');
      }
    ],
    data() {
      return {
        msg: 'Hello Vue'
      };
    },
    template: '<div>{{msg}}</div>'
  }
};
export function initMixin(Vue: GlobalAPI) {
  Vue.mixin = function(mixin: Object) {
    this.options = mergeOptions(this.options, mixin);
    return this;
  };
}

先通过 Vue.mixin 把 create 钩子混入到 Vue.options 中,然后执行 new Vue(options),然后执行 Vue._init

外部调用场景

  • 1._init
// src/core/instance/init.js
// 不同场景对于options的合并逻辑是不一样的,传入的options也不一样
//
Vue.prototype._init = function(options?: Object) {
  const vm: Component = this;
  // a uid
  vm._uid = uid++;

  let startTag, endTag;
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    startTag = `vue-perf-start:${vm._uid}`;
    endTag = `vue-perf-end:${vm._uid}`;
    mark(startTag);
  }

  // a flag to avoid this being observed
  vm._isVue = true;
  // 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 {
    // 当执行 new Vue的时候,会执行this._init(options)
    // resolveConstructorOptions(vm.constructor) 的返回值和 options 做合并
    // 在以下例子下,只是简单的返回vm.constructor.options,相当于Vue.options
    // Vue.options在initGlobalAPI(Vue) 中定义了,【见2.initGlobalAPI】
    // 再见【3.mergeOptions】
    vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);
  }
  // ...
};
  • 2.initGlobalAPI
// src/core/global-api/index.js
export function initGlobalAPI(Vue: GlobalAPI) {
  // ...
  // 首先建一个空对象
  Vue.options = Object.create(null);
  // 然后遍历ASSET_TYPES
  // export const ASSET_TYPES = [
  // 'component',
  // 'directive',
  // 'filter'
  // ]
  ASSET_TYPES.forEach((type) => {
    Vue.options[type + 's'] = Object.create(null);
  });
  // 遍历后的结果
  /*
    Vue.options.components = {}
    Vue.options.directives = {}
    Vue.options.filters = {}
  */
  // 为了扩展普通对象,weex方式用
  Vue.options._base = Vue;
  // 通过下面方法,把一些内置组件扩展到Vue.options.components上
  // Vue的内置组件有<keep-alive>、<transition> 和 <transition-group> 组件
  extend(Vue.options.components, builtInComponents);

  // ...
}
  • 3.mergeOptions
// src/core/util/options.js
// mergeOptions的主要功能是,把parent和child两个对象,进行合并,合成一个新对象返回
// 如果是组件场景,那么parent就存在,如果是外部常见,parent不存在
export function mergeOptions(parent: Object, child: Object, vm?: Component): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child);
  }

  if (typeof child === 'function') {
    child = child.options;
  }

  normalizeProps(child, vm);
  normalizeInject(child, vm);
  normalizeDirectives(child);
  const extendsFrom = child.extends;
  // 核心步骤1:递归把extends和mixins合并到parent
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm);
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm);
    }
  }
  const options = {};
  let key;
  // function mergeHook(parentVal: ?Array<Function>, childVal: ?Function | ?Array<Function>): ?Array<Function> {
  //   return childVal ? (parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal]) : parentVal;
  // }

  // LIFECYCLE_HOOKS.forEach((hook) => {
  //   strats[hook] = mergeHook;
  // });
  // 核心步骤2:然后遍历parent,调用mergeField
  for (key in parent) {
    mergeField(key);
  }
  // 核心步骤2:然后再遍历child,如果key不在parent的自身属性上,则调用mergeField
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key);
    }
  }
  // mergeField对不同key有着不同的合并策略
  // 通过执行mergeField 函数,把合并后的结果保存在options对象中,最终返回
  function mergeField(key) {
    const strat = strats[key] || defaultStrat;
    // 如果child和parent都定义了相同的钩子函数,那么就会把两个钩子函数合并成一个数组
    options[key] = strat(parent[key], child[key], vm, key);
  }
  return options;
  // 回到【以此为例】
}

组件场景

  • 1.Vue.extend
// src/core/global-api/extend.js
Vue.extend = function(extendOptions: Object): Function {
  // ...
  // extendOptions就是组件对象
  // Vue.options 最终合并到 Sub.opitons
  Sub.options = mergeOptions(Super.options, extendOptions);

  Sub.superOptions = Super.options;
  Sub.extendOptions = extendOptions;
  Sub.sealedOptions = extend({}, Sub.options);

  // ...
  return Sub;
};
  • 2.组件初始化过程 createComponentInstanceForVnode
// src/core/vdom/create-component.js
export function createComponentInstanceForVnode(
  vnode: any, // we know it's MountedComponentVNode but flow doesn't
  parent: any // activeInstance in lifecycle state
): Component {
  const options: InternalComponentOptions = {
    _isComponent: true,
    _parentVnode: vnode,
    parent
  };
  // vnode.componentOptions.Ctor指向Vue.extend的返回值Sub,
  // 所以执行new的过程,接着执行 this._init(options)
  // 又因为 options._isComponent 为true
  // 就会走到initInternalComponent(vm, options) 见【3.initInternalComponent】
  return new vnode.componentOptions.Ctor(options);
}
  • 3.initInternalComponent
// src/core/instance/init.js
// 只是做了简单一层对象赋值,并不涉及到递归、合并策略等复杂逻辑
export function initInternalComponent(vm: Component, options: InternalComponentOptions) {
  // vm.constructor.options就是构造函数Sub
  // 相当于vm.$options = Sub.options
  const opts = (vm.$options = Object.create(vm.constructor.options));
  // 把实例化子组件传入子组件父Vnode实例parentVnode
  const parentVnode = options._parentVnode;
  // 子组件的父Vue实例parent保存在vm.$options中
  opts.parent = options.parent;
  // 保留了 parentVnode 配置中的其它的属性
  opts._parentVnode = parentVnode;
  //
  const vnodeComponentOptions = parentVnode.componentOptions;
  opts.propsData = vnodeComponentOptions.propsData;
  opts._parentListeners = vnodeComponentOptions.listeners;
  opts._renderChildren = vnodeComponentOptions.children;
  opts._componentTag = vnodeComponentOptions.tag;

  if (options.render) {
    opts.render = options.render;
    opts.staticRenderFns = options.staticRenderFns;
  }
  // 回到【以此为例】
}

暂无评论