资讯专栏INFORMATION COLUMN

人人都能懂的Vue源码系列—06—mergeOptions-下

megatron / 980人阅读

摘要:下面分别为大家介绍钩子函数的策略所有关于钩子函数的策略,其实都是调用方法。的策略介绍完了钩子函数的合并策略,我们接下来看等属性的合并策略。如果当前实例或者构造函数上有一个没有属性,则返回另一个的属性,如果两者都有,则同样调用方法处理合并。

上篇文章,我们讲到了mergeOptions的部分实现,今天接着前面的部分讲解,来看代码,如果大家觉得看讲解枯燥可以直接翻到本文的最后看mergeOptions的整个流程图。

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

这段代码的处理的逻辑是,当传入的options里有mixin或者extends属性时,再次调用mergeOptions方法合并mixins和extends里的内容到实例的构造函数options上(即parent options)比如下面这种情况

 const childComponent = Vue.component("child", {
      ...
      mixins: [myMixin],
      extends: myComponent
      ...
 })
 const myMixin = {
      created: function () {
        this.hello()
      },
      methods: {
        hello: function () {
          console.log("hello from mixin")
      }
    }
  }
 const myComponent = {
      mounted: function () {
        this.goodbye()
      },
      methods: {
        goodbye: function () {
          console.log("goodbye from mixin")
        }
     }
  }

就会把传入的mounted, created钩子处理函数,还有methods方法提出来去和parent options做合并处理。
弄明白了这点我们继续回到mergeOptions的代码

const options = {}
let key

变量options存储合并之后的options,变量key存储parent options和child options上的key值。
接下来的部分算是mergeOptions方法的核心处理部分了,像炒菜一样,前面的代码相当于把所有的菜都配好了。接下来的部分就是教你怎么去炒菜了。

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

前两段for循环代码大同小异,都是遍历options上的key值,然后调用mergeField方法来处理options。mergeField方法中出现了一个变量strats和defaultStrat。这两个变量存储的就是我们的合并策略,也就是炒菜的菜谱,我们先来看看defaultStrat

const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}

defaultStrat的逻辑是,如果child上该属性值存在时,就取child上的该属性值,如果不存在,则取parent上的该属性值。现在我们知道默认的合并策略是什么了,接下来看其他的合并策略。我们来看看strats里都有哪些属性?

上图就是strats中所有的策略了。粗略看起来里面的内容非常的多,如果细细分析会发现,其实总结起来无非就是几种合并策略。下面分别为大家介绍

钩子函数的策略

所有关于钩子函数的策略,其实都是调用mergeHook方法。

function mergeHook (
  parentVal: ?Array,
  childVal: ?Function | ?Array
): ?Array {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

mergeHook采用了一个非常骚的嵌套三元表达式来控制最后的返回值。下面我们来解析这段三元表达式
(1) child options上不存在该属性,parent options上存在,则返回parent上的属性。

(2)child和parent都存在该属性,则返回concat之后的属性

(3)child上存在该属性,parent不存在,且child上的该属性是Array,则直接返回child上的该属性

(4) child上存在该属性,parent不存在,且child上的该属性不是Array,则把该属性先转换成Array,再返回。


上面就是钩子函数合并策略,结合图片看应该会比较清晰。

props/methods/inject/computed的策略

介绍完了钩子函数的合并策略,我们接下来看props,methods,inject,computed等属性的合并策略。

strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  if (childVal && process.env.NODE_ENV !== "production") {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)
  return ret
}

这个合并方法逻辑很简单,如果child options上这些属性存在,则先判断它们是不是对象。
(1)如果parent options上没有该属性,则直接返回child options上的该属性
(2)如果parent options和child options都有,则合并parent options和child options并生成一个新的对象。(如果parent和child上有同名属性,合并后的以child options上的为准)

components/directives/filters的合并策略

components/directives/filters这几个属性的处理逻辑如下

function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): Object {
  const res = Object.create(parentVal || null)
  if (childVal) {
    process.env.NODE_ENV !== "production" && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}

这里的处理逻辑和上一种情况的类似,这里不做过多讲解。

data和provide的策略

data和provide的策略相对来说复杂一些,我们先来看代码

export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    // in a Vue.extend merge, both should be functions
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    // when parentVal & childVal are both present,
    // we need to return a function that returns the
    // merged result of both functions... no need to
    // check if parentVal is a function here because
    // it has to be a function to pass previous merges.
    return function mergedDataFn () {
      return mergeData(
        typeof childVal === "function" ? childVal.call(this, this) : childVal,
        typeof parentVal === "function" ? parentVal.call(this, this) : parentVal
      )
    }
  } else {
    return function mergedInstanceDataFn () {
      // instance merge
      const instanceData = typeof childVal === "function"
        ? childVal.call(vm, vm)
        : childVal
      const defaultData = typeof parentVal === "function"
        ? parentVal.call(vm, vm)
        : parentVal
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

这个合并策略可以分成两种情况来考虑。
第一种情况,当前调用mergeOptions操作的是vm实例(调用new新建vue实例触发mergeOptions方法)

   return function mergedInstanceDataFn () {
      // instance merge
      const instanceData = typeof childVal === "function"
        ? childVal.call(vm, vm)
        : childVal
      const defaultData = typeof parentVal === "function"
        ? parentVal.call(vm, vm)
        : parentVal
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }

如果新建实例时传入的options上有data属性,则调用mergeData方法合并实例上的data属性和其构造函数options上的data属性(如果有的话)
第二种情况,当前调用mergeOptions操作的不是vm实例(即通过Vue.extend/Vue.component调用了mergeOptions方法)

 if (!vm) {
    // in a Vue.extend merge, both should be functions
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    // when parentVal & childVal are both present,
    // we need to return a function that returns the
    // merged result of both functions... no need to
    // check if parentVal is a function here because
    // it has to be a function to pass previous merges.
    return function mergedDataFn () {
      return mergeData(
        typeof childVal === "function" ? childVal.call(this, this) : childVal,
        typeof parentVal === "function" ? parentVal.call(this, this) : parentVal
      )
    }
  }

在这种情况下,其处理逻辑也是类似的。如果当前实例options或者构造函数options上有一个没有data属性,则返回另一个的data属性,如果两者都有,则同样调用mergeData方法处理合并。
既然这两种情况都调用了mergeData方法,那我们就继续来看看mergeData的源码

function mergeData (to: Object, from: ?Object): Object {
  if (!from) return to
  let key, toVal, fromVal
  const keys = Object.keys(from)
  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    toVal = to[key]
    fromVal = from[key]
    if (!hasOwn(to, key)) {
      set(to, key, fromVal)
    } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}

mergeData的逻辑是,如果from对象中有to对象里没有的属性,则调用set方法,(这里的set就是Vue.$set,先可以简单理解为对象设置属性。之后会细讲)如果from和to中有相同的key值,且key对应的value是对象,则会递归调用mergeData方法,否则以to的值为准,最后返回to对象。这里我们就讲完了data的合并策略。
返回mergeOptions代码里,在经过这几种合并策略合并options后,最终返回options

return options
总结

讲到这里,整个mergeOptions的流程也讲完了。这个方法牵扯到的内容比较多,流程也比较复杂。为了大家更好的理解和记忆。我画了一张图来表达整个mergeOptions的过程。

如果大家觉得我的文章写的还行,请为我点赞,你们的认可是我最大的动力。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94730.html

相关文章

  • 人人都能懂的Vue源码系列—05—mergeOptions-上

    摘要:在解析完其构造函数上的之后,需要把构造函数上的和实例化时传入的进行合并操作并生成一个新的。检查组件名称是否合法首先看传入的三个参数,,这三个参数分别代表的是该实例构造函数上的实例化时传入的实例本身。 前几篇文章中我们讲到了resolveConstructorOptions,它的主要功能是解析当前实例构造函数上的options,不太明白的同学们可以看本系列的前几篇文章。在解析完其构造函数...

    iKcamp 评论0 收藏0
  • 人人都能懂的Vue源码系列—04—resolveConstructorOptions函数-

    摘要:上一篇文章中说道,函数要分两种情况进行说明,第一种是为基础构造器的情况,这个已经向大家介绍过了,今天这篇文章主要介绍第二种情况,是创建的子类。表示的是当前构造器上新增的,表示的是当前构造器上新增的封装。 上一篇文章中说道,resolveConstructorOptions函数要分两种情况进行说明,第一种是Ctor为基础构造器的情况,这个已经向大家介绍过了,今天这篇文章主要介绍第二种情况...

    My_Oh_My 评论0 收藏0
  • 人人都能懂的Vue源码系列—04—resolveConstructorOptions函数-

    摘要:上一篇文章中说道,函数要分两种情况进行说明,第一种是为基础构造器的情况,这个已经向大家介绍过了,今天这篇文章主要介绍第二种情况,是创建的子类。表示的是当前构造器上新增的,表示的是当前构造器上新增的封装。 上一篇文章中说道,resolveConstructorOptions函数要分两种情况进行说明,第一种是Ctor为基础构造器的情况,这个已经向大家介绍过了,今天这篇文章主要介绍第二种情况...

    BlackHole1 评论0 收藏0
  • 人人都能懂的Vue源码系列—02—Vue构造函数

    摘要:果然我们找到了的构造函数定义。告诉你是一个构造函数,需要用操作符去调用。在深入方法之前,我们先把目光移到文件里在的构造函数定义之后,有一系列方法会被立即调用。下篇博文主要介绍相关的内容,涉及到原型链和构造函数以及部分的实现,敬请期待 上篇博文中说到了Vue源码的目录结构是什么样的,每个目录的作用我们应该也有所了解。我们知道core/instance目录主要是用来实例化Vue对象,所以我...

    X_AirDu 评论0 收藏0
  • 人人都能懂的Vue源码系列—03—resolveConstructorOptions函数-上

    摘要:上篇文章介绍了构造函数的部分实现,当前实例不是组件时,会执行方法。这个文件就是对构造函数进行的第一层包装了。但是注意这里的代码我们构造函数的第二层包装,就在这个文件里了。回到的源码中,当不存在时,直接返回基础构造器的。 上篇文章介绍了Vue构造函数的部分实现,当前Vue实例不是组件时,会执行mergeOptions方法。 vm.$options = mergeOptions( re...

    snifes 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<