lihongxun945/myblog

Vue2.x源码解析系列三:Options配置的处理

lihongxun945 opened this issue · 3 comments

在正式说数据响应化之前,先让我们简单说下options的处理,其实对options的处理过程也挺复杂,但是这些细节并不是本文关注的重点,所这里我们只挑它的主要代码讲,至于一些细节比如如何进行 normalize 等有兴趣的话可以自己看看源码。

正如上图所示,我们上一章讲到过,在 _init 函数中,有这么一段代码进行 options 的合并,生成一个新的 this.$options。当然实际情况比这个图要复杂些。

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

这一段代码主要调用了两个函数 resolveConstructorOptionsmergeOptions,让我们从调用顺序来分别看看这两个函数的作用。

resolveConstructorOptions 会递归向super查找 options,如果找到了,那么就把父类的options和当前的options进行合并。

export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  if (Ctor.super) {
    // 把父类的options找出来,因为可能父类也有父类,因此这里是递归查找,把`parents`上的所有options都合并到当前。
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions

    //如果找到了,那么就把父类的 `options`和当前的 `options`进行一次合并。
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
options.components[options.name] = Ctor //如果指定了`name`,那么在自己身上注册一下自己。这样就可以直接在模板中用了。
      }
    }
  }
  return options
}

可能大家会有疑问,哪里来的父类呢?我们一般有两种方式来创建一个Vue组件:

//最常见的方式是直接创建,这种情况下不需要处理父类
new Vue(options)

//如果我们有一些常用的默认配置,我们可以自己创建一个自定义的类,此时,我们就需要进行 `options`合并了,不然我们自己创建的这个类上就没有 `MyVue.options`了。

const MyVue = Vue.extend(options)
new MyVue()

那么我们再看第二个函数,也就是 mergeOptions,顾名思义,就是把几个不同的 options合并成同一个,mergeOptions函数的定义在 core/util/options.js中。因为options中有很多字段,不同字段的处理方式会不同,比如有些字段就会子类覆盖父类,有些字段可能就需要把值合并起来。我们先看看 mergeOptions 函数:

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
  }

  // normalize,归一化处理,这是什么意思呢?
  // 因为vue为了方便使用,同样的定义可以以不同的形式写出来,比如 `props:[‘name’]` 和 `props: { name: { type: String}}` 都是可以的,在解析这些配置之前,就要先把配置的格式统一一下以方便处理
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  // 把孩子中的合并
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }

  // mixins一般也是一个 options,因此也要处理。
  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
  // 这里真的开始进行父子组件的merge操作了。注意,不同的filed是有不同方案的,比如到底是要覆盖还是要合并等。`mergeFiled`函数是通过调用 `strats`上定义好的一些函数规则来实现的,他对不同的 `key` 有不同的规则,比如 `listeners` 就要进行数组的合并,`data` 就要进行 `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
}

好了,我们已经大致知道了 Vue 是如何处理 options的,下面让我们正式进入数据响应化吧。

下一篇:Vue2.x源码解析系列四:数据响应之Observer

“至于一些细节比如如何进行 normailize 等有兴趣的话可以自己看看源码。” 上面有个单词拼错了

@VimMing 已更正,感谢

"不同的filed是有不同方案的,比如到底是要覆盖还是要合并等。mergeFiled函数是通过调用……"。filed -> field。