Vue源码系列12-------合并配置----外部调用

new Vue初始化的过程通常有 2 种场景,一种是外部我们的代码主动调用 new Vue(options) 的方式实例化一个 Vue 对象;另一种是组件过程中内部通过 new Vue(options) 实例化子组件。但是无论哪种场景,都会执行实例的 _init(options) 方法,它首先会执行一个 merge options 的逻辑,相关的代码在 src/core/instance/init.js 中:


Vue.prototype._init = function (options?: Object) {
  // 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
    )
  }
  // ...
}

从上面可以看到不同场景对于 options 的合并逻辑是不一样的,并且传入的 options 值也有非常大的不同,接下来我会分开介绍 2 种场景的 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 的时候,在执行 this._init(options) 的时候,就会执行如下逻辑去合并 options

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)

这里通过调用 mergeOptions 方法来合并,它实际上就是把 resolveConstructorOptions(vm.constructor) 的返回值和 options 做合并,resolveConstructorOptions 的实现先不考虑,在我们这个场景下,它还是简单返回 vm.constructor.options,相当于 Vue.options,那么这个值又是什么呢,其实在 initGlobalAPI(Vue) 的时候定义了这个值,代码在 src/core/global-api/index.js 中:

export function initGlobalAPI (Vue: GlobalAPI) {
  // ...
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)
  // ...
}

以上可以看出 Vue.options = Object.create(null) 创建一个空对象,然后遍历 ASSET_TYPESASSET_TYPES 的定义在 src/shared/constants.js 中:

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

所以上面遍历 ASSET_TYPES 后的代码相当于:

Vue.options.components = {}
Vue.options.directives = {}
Vue.options.filters = {}

接着执行了 Vue.options._base = Vue,最后通过 extend(Vue.options.components, builtInComponents) 把一些内置组件扩展到 Vue.options.components 上,Vue 的内置组件目前有 <keep-alive><transition> 和 <transition-group> 组件。

那么回到 mergeOptions 这个函数,它的定义在 src/core/util/options.js 中:

/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
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
  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
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

mergeOptions 主要功能就是把 parent 和 child 这两个对象根据一些合并策略,合并成一个新对象并返回。比较核心的几步,先递归把 extends 和 mixixns 合并到 parent 上,然后遍历 parent,调用 mergeField,然后再遍历 child,如果 key 不在 perent 的自身属性上,则调用 mergeField。关于 mergeField 函数,它对不同的 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
})

这其中的 LIFECYCLE_HOOKS 的定义在 src/shared/constants.js 中:

export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
]

在这个文件中定义了 Vue.js 所有的钩子函数名称,所以对于钩子函数,他们的合并策略都是 mergeHook 函数。这个函数的实现用了一个多层 3 元运算符,逻辑就是如果不存在 childVal ,就返回 parentVal;否则再判断是否存在 parentVal,如果存在就把 childVal 添加到 parentVal 后返回新数组;否则返回 childVal 的数组。所以回到 mergeOptions 函数,如果 parent 和 child 都定义了相同的钩子函数,那么它们会把 2 个钩子函数合并成一个数组。【关于其它属性的合并策略的定义都可以在 src/core/util/options.js 文件中看到。】通过执行 mergeField 函数,把合并后的结果保存到 options 对象中,最终返回它。因此,在我们当前这个 case 下,执行完如下合并后:

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)

vm.$options 的值差不多是如下这样:

vm.$options = {
  components: { },
  created: [
    function created() {
      console.log('parent created') 
    }
  ],
  directives: { },
  filters: { },
  _base: function Vue(options) {
    // ...
 },
  el: "#app",
  render: function (h) {  
    //...
  }
}

 

 

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值