Vue源码分析 —— 选项合并策略(2)

选项合并策略分析(2)

选项合并策略分析(1)

生命周期钩子函数合并策略

在 Vue 中,生命周期是一个很重要的知识点,我们可以在生命周期各个阶段做不同的事情。现在我们来看一下 Vue 生命周期钩子函数的合并策略

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  /**
   * 合并策略
   * 1. 子类不存在钩子选项时,使用父类钩子选项
   * 2. 子类存在钩子选项,父类不存在钩子选项时,使用子类钩子选项
   * 3. 子类和父类都存在钩子选项时,进行合并,将子类的钩子选项放在数组末尾,在执行钩子函数时,父类的钩子函数优先于子类执行
   */
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res
    ? dedupeHooks(res)
    : res
}

function dedupeHooks (hooks) {
  const res = []
  for (let i = 0; i < hooks.length; i++) {
    if (res.indexOf(hooks[i]) === -1) {
      res.push(hooks[i])
    }
  }
  return res
}

在这里解释一下上面的三元表达式的执行逻辑:

    1. 子类钩子函数不存在时,使用父类的钩子函数
    1. 子类的钩子函数存在父类钩子不存在,使用子类的钩子函数,(如果子类的钩子函数不是数组,则转换成数组)
    1. 子类和父类的钩子函数都存在时,将子类的钩子函数添加到数组末尾

总结一下:钩子函数在合并时,子类和父类相同的选项会合并成数组,数组的每一项都是一个函数 在钩子函数执行时,父类钩子函数也会执行,并且由于子类钩子执行

举例说明
const parent = Vue.extend({
  data(){
    return {
      a: 1
    }
  },
  created(){
    console.log("aaaaa")
  }
})

var sub = parent.extend({
  // el:"#root",  子类中不允许包含 el 选项
  data(){
    return {
      b: 2
    }
  },
  created(){
    console.log("bbbbb")
  }
})

var subVm = new sub()
console.log(subVm.$data)
subVm.$mount("#root")

上面的例子中, parent 实例中的钩子函数 created 会与 Vue 构造器器中的钩子函数(此时为 undefined)进行合并,得到的结果为 [function created]

sub 实例会与 parent 中的钩子函数进行合并,此时 parent 的钩子函数选项为 [function created],合并后 sub 实例的 options 中的 created 钩子函数为 [function created, function created](其中第一个为 parent 中钩子函数,第二个为 sub 自身的钩子函数)。 最终得到的 sub 实例中 options 选项如下图:

生命周期钩子函数合并结果

watch 选项合并

strats.watch = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  // work around Firefox's Object.prototype.watch...
  // 火狐浏览器在 Object 原型上拥有 watch ,进行兼容, export const nativeWatch = ({}).watch
  if (parentVal === nativeWatch) parentVal = undefined
  if (childVal === nativeWatch) childVal = undefined
  /* istanbul ignore if */
  if (!childVal) return Object.create(parentVal || null)
  if (process.env.NODE_ENV !== 'production') {
    // watch 选项必须是对象
    assertObjectType(key, childVal, vm)
  }
  // 父类的 watch 选项不存在,则直接使用子类自身的 watch 选项
  if (!parentVal) return childVal
  const ret = {}
  // 首先将父类的 watch 选项赋值给 ret 对象,然后便利子类的选项,
  extend(ret, parentVal)
  for (const key in childVal) {
    let parent = ret[key]
    const child = childVal[key]
    // 先将父类 watch 选项中对 [key] 属性监听的 handler 方法转换成数组(如果存在的话)
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    ret[key] = parent
      ? parent.concat(child)
      : Array.isArray(child) ? child : [child]
  }
  return ret
}

对于 watch 选线的合并策略和生命周期钩子函数合并策略有些类似,只要父类选项中存在相同的观测字段,则和子类的选项合并成数组。由于 watch 选项有多种写法,合并之后的选项也存在多种情况

  • 合并后的 watch 选项是一个对象,键为监听的属性名,值为回掉函数
  • 合并后的 watch 选项是一个对象数组,数组每一项的键为监听的属性名,值为回掉函数
  • 合并后的 watch 选项是一字符串/字符串数组(字符串为函数名,该方法在 methods 选项中进行定义)
举例
const parent = Vue.extend({
  data(){
    return {
      a: 1
    }
  },
  watch:{
    a: ['achange', 'change1']
  },
  methods:{
    achange(){
      console.log("achange")
    },
    change1(){
      console.log('aaaaaa')
    }
  }

})

var sub = parent.extend({
  // el:"#root",  子类中不允许包含 el 选项
  data(){
    return {
      b: 2
    }
  },

  watch:{
    b: {
      handler(newVal, oldVal){
        console.log(newVal)
      }
    },
    a:{
      handler(newVal, oldVal){
        console.log("sub watch a change")
        console.log(newVal)
      }
    }
  }
})

上面的例子中,最终合并之后, parent 中 watch 选项如下图所示:

parent watch 选项

sub 中 watch 选项如下图所示

sub watch 选项

props methods inject computed 合并策略

props、methods、inject、computed 这些选项的数据类型都是对象(props 和 inject 在选项规范话时已经转换成对象格式),合并策略是相同的,也相对比较简单。
如果父类选项不存在,则使用子类选项。如果子类和父类选项同时存在,使用子类选项覆盖父类选项

// 代码位置 core/util/options.js
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
}

provide 合并策略

provide 的合并策略和 data 合并策略一致,不同点就是 provide 没有规定必须时函数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值