美文网首页
合并配置

合并配置

作者: 小螃蟹_5f4c | 来源:发表于2019-10-09 15:29 被阅读0次

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)
})

在这首先会是调用全局的mixin 这个函数如下

function initMixin$1(Vue) {
    Vue.mixin = function(mixin) {
        this.options = mergeOptions(this.options, mixin);
        return this
    };
}

Vue.options,那么这个值又是什么呢,其实在 initGlobalAPI(Vue) 的时候定义了这个值,这个函数在初始化的时候就执行了, 所以Vue.options这个时候就有值了,代码在 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_TYPES,ASSET_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> 组件,这也就是为什么我们在其它组件中使用 <keep-alive> 组件不需要注册的原因,这块儿后续我们介绍 <keep-alive> 组件的时候会详细学习
这里就调用了mergeOption函数来合并Vue本身的和mixin上的传入的对象
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 文件中看到,这里不介绍了
因为例子中的是created的 所以合并后的vue.options多了一个created的函数数组,数组里面就是mixin中的created函数
之后就是进行new Vue的调用 进入_init函数 会进入如下逻辑

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

这里通过调用 mergeOptions 方法来合并,它实际上就是把 resolveConstructorOptions(vm.constructor) 的返回值和 options 做合并,resolveConstructorOptions 的实现先不考虑,在我们这个场景下,它还是简单返回 vm.constructor.options,
这个时候的Vue.option就是上面合并过mixin得包含created函数数组的对象,这里传入的optiond就是类似如下:

{
  el: '#app',
  render: h => h(childComp)
}

因此,在我们当前这个 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) {  
    //...
  }
}

继续往下执行
组件场景
由于组件的构造函数是通过 Vue.extend 继承自 Vue 的,先回顾一下这个过程,代码定义在 src/core/global-api/extend.js 中

/**
 * Class inheritance
 */
Vue.extend = function (extendOptions: Object): Function {
  // ...
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )

  // ...
  // keep a reference to the super options at extension time.
  // later at instantiation we can check if Super's options have
  // been updated.
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  Sub.sealedOptions = extend({}, Sub.options)

  // ...
  return Sub

我们只保留关键逻辑,这里的 extendOptions 对应的就是前面定义的组件对象,它会和 Vue.options 合并到 Sub.opitons 中。
这里的Vue.option还是刚开始的合并了mixin的那个对象 合并之后的created就是一个函数数组,里面两个函数 第一个是mixin的 第二个是组件的 。所以在实际使用的合并后的钩子函数 mixin中的总是先执行 组件的后执行。
接下来我们再回忆一下子组件的初始化过程,代码定义在 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
  }
  // ...
  return new vnode.componentOptions.Ctor(options)
}

这里的 vnode.componentOptions.Ctor 就是指向 Vue.extend 的返回值 Sub, 所以 执行 new vnode.componentOptions.Ctor(options) 接着执行 this._init(options),因为 options._isComponent 为 true,那么合并 options 的过程走到了 initInternalComponent(vm, options) 逻辑。先来看一下它的代码实现,在 src/core/instance/init.js 中:

export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  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
  }
}

initInternalComponent 方法首先执行 const opts = vm.$options = Object.create(vm.constructor.options),这里的 vm.construction 就是子组件的构造函数 Sub,相当于 vm.$options = Sub.options。
接着又把实例化子组件传入的子组件父 VNode 实例 parentVnode、子组件的父 Vue 实例 parent 保存到 vm.$options 中,另外还保留了 parentVnode 配置中的如 propsData 等其它的属性。
这么看来,initInternalComponent 只是做了简单一层对象赋值,并不涉及到递归、合并策略等复杂逻辑。
所以在通过initInternalComponent(vm, options)合并后
vm.$options 的值差不多是如下这样:

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>'
  }
}

简单来说,这个组件合并的逻辑主要分为两步 第一步在初始化组件构造函数的时候执行一次mergeOption将Vue.option上的合并; 第二步在init函数里面再合并一些属性

总结
那么至此,Vue 初始化阶段对于 options 的合并过程就介绍完了,需要知道对于 options 的合并有 2 种方式,子组件初始化过程通过 initInternalComponent 方式要比外部初始化 Vue 通过 mergeOptions 的过程要快,合并完的结果保留在 vm.$options 中。
纵观一些库、框架的设计几乎都是类似的,自身定义了一些默认配置,同时又可以在初始化阶段传入一些定义配置,然后去 merge 默认配置,来达到定制化不同需求的目的。只不过在 Vue 的场景下,会对 merge 的过程做一些精细化控制,虽然我们在开发自己的 JSSDK 的时候并没有 Vue 这么复杂,但这个设计思想是值得借鉴的。

相关文章

  • 配置合并

    概述 通过之前章节的源码分析我们知道,new Vue的过程通常有 2 种场景,一种是外部我们的代码主动调用new ...

  • 合并配置

    new Vue 的过程通常有 2 种场景,一种是我们的代码主动调用 new Vue(options) 的方式实例化...

  • 如何做好python项目的配置分离

    本文内容: 项目配置分离 为什么要分离 该怎么分离 配置合并 合并代码 分离 为什么要分离: 为区分正式和开发而分...

  • Git 常用命令

    Git设置代理 Git分支操作 Git合并 Git 配置

  • Spark writer

    对UnsafeShuffleWriter可优化配置主要在最终多个spill合并时,input和output缓存配置...

  • python库实战 - openpyxl

    一、安装 二、脚本背景 做游戏测试的都知道,很多功能的实现依赖策划的配置。然后在进行版本合并的时候,配置表的合并是...

  • 命令

    git commit 用户信息: 全局配置: 查看配置是否正常 git代码提交 git tag 分支与合并 贮藏 ...

  • git 合并代码常见问题

    git 合并代码常见问题 配置文件出现冲突 导致项目无法打开--> 找合并前的(bidHall.xcodeproj...

  • android全平台编译ffmpeg合并为单个库实践

    目录 编译环境 配置config.sh脚本 配置合并脚本build_ffmpeg_merge.sh 脚本地址 编译...

  • Ngrok 客户端连接配置

    HTTP 配置 TCP 配置 合并 启动 推荐 手把手教你搭建ngrok服务-轻松外网调试内网服务[https:/...

网友评论

      本文标题:合并配置

      本文链接:https://www.haomeiwen.com/subject/otcgpctx.html