Vue2.x选项合并策略一(源码解读)

5 篇文章 0 订阅
2 篇文章 0 订阅

什么是选项合并策略
在Vue中,使用mixins的时候发现:

  • 混入的methods里的方法如果与组件里的方法同名时,会被组件的方法覆盖;
  • 但生命周期函数如果重名,混入的生命周期函数与组件本身的都会被执行,执行顺序为先执行混入后执行自身。

这主要与Vue中的选项合并策略有关,不同的选项有不同的合并策略。

例如:
data、props、methods都是同属性直接覆盖合并;
created、mounted等生命周期函数都是直接合并,同名的函数存放在一个数组中,依次进行调用。

Vue提供一个api:

Vue.config.optionMergeStrategies

可以通过这个api实现自定义的选项合并策略。

在代码中打印:

console.log(Vue.config.optionMergeStrategies)

可以看出,不同的选项都有各自的合并策略
在这里插入图片描述

tips:以下父组件选项可理解为mixins混入的数据;子组件选项为当前自身组件选项。

默认合并策略 defaultStrat

解读源码:
传入两个参数parentVal、childVal分别对应父组件和子组件的选项;
采用的合并的策略为子组件选项不存在时返回父组件选项,否则返回子组件。

/**
 * Default strategy.
 */
const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined ? parentVal : childVal
}

el、propsData
以此可以看出:el和propsData的合并策略就是属于默认合并策略。

/**
 * Options with restrictions
 */
if (process.env.NODE_ENV !== 'production') {
  strats.el = strats.propsData = function (parent, child, vm, key) {
    if (!vm) {
      warn(`option "${key}" can only be used during instance ` + 'creation with the `new` keyword.')
    }
    return defaultStrat(parent, child)
  }
}

props、methods、computed、inject

源码解读:
若父组件选项不存在,直接返回子组件选项;
否则,创建一个空对象ret,父组件选项覆盖ret属性;若子组件选项存在,则覆盖父组件选项中同名的属性。
即:父子组件选项中存在同名属性,则子组件选项属性覆盖父组件选项属性。

/**
 * Other object hashes.
 */
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
}

/** 遍历合并数据,子组件选项同名属性覆盖父组件
 * Mix properties into target object.
 */
export function extend (to: Object, _from: ?Object): Object {
  for (const key in _from) {
    to[key] = _from[key]
  }
  return to
}

data
源码解读:
如果传入参数‘vm’,则表示组件为根实例。
若子组件选项‘data’存在但是不为function类型,直接返回父组件选项;
否则进行合并策略。

strats.data = function (parentVal: any, childVal: any, vm?: Component): ?Function {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )
      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }
  return mergeDataOrFn(parentVal, childVal, vm)
}

源码解读:
若子组件data不存在,返回父组件data;若父组件data不存在,返回子组件data。
进行合并策略。

/**
 * Data
 */
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
    }
    // 若组件选项类型为function,需要使用call()指向当前this
    return function mergedDataFn () {
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }
  } else {
    // parent为根实例
    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
      }
    }
  }
}

源码解读:
递归合并两个数据对象

/**
 * Helper that recursively merges two data objects together.
 */
function mergeData (to: Object, from: ?Object): Object {
  // 若不存在父组件数据,无需合并,直接返回子组件数据
  if (!from) return to
  let key, toVal, fromVal
  // 获取父组件数据属性对应key值,返回一个key集合数组
  const keys = hasSymbol ? Reflect.ownKeys(from) : Object.keys(from)
  // 遍历父组件数据keys
  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    // in case the object is already observed...
    if (key === '__ob__') continue
    // 获取子组件属性key的value值
    toVal = to[key]
    // 获取父组件属性key的value值
    fromVal = from[key]
    // 如果子组件数据对象中不存在该key,如果存在则不做改动
    if (!hasOwn(to, key)) {
      // 将父组件key对应的value值赋值给子组件数据属性key
      set(to, key, fromVal)
    // 如果父子组件当前key对应的value不相等,且为Object类型时,进行递归
    } else if (toVal !== fromVal && isPlainObject(toVal) && isPlainObject(fromVal)) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}
// 判断浏览器是否支持某一方法 [native code] 如ES6新特性‘Symbol’、‘Reflect’等
/* istanbul ignore next */
export function isNative (Ctor: any): boolean {
  return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}

export const hasSymbol =
  typeof Symbol !== 'undefined' && isNative(Symbol) &&
  typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)
/**
 * Get the raw type string of a value, e.g., [object Object].
 */
const _toString = Object.prototype.toString

/**
 * Strict object type check. Only returns true
 * for plain JavaScript objects.
 */
export function isPlainObject (obj: any): boolean {
  return _toString.call(obj) === '[object Object]'
}

watch
源码解读:
实现合并策略,不进行数据覆盖;合并在一个数组,先执行父组件数据,再执行子组件数据。

/**
 * Watchers.
 * Watchers hashes should not overwrite one
 * another, so we merge them as arrays.
 */
strats.watch = function (parentVal: ?Object, childVal: ?Object, vm?: Component, key: string): ? Object {
  // work around Firefox's Object.prototype.watch...
  // 此处nativeWatch相当于undefined
  if (parentVal === nativeWatch) parentVal = undefined
  if (childVal === nativeWatch) childVal = undefined
  /* istanbul ignore if */
  // 如果子组件数据不存在,返回父组件数据对象 || null
  if (!childVal) return Object.create(parentVal || null)
  if (process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  // 如果父组件不存在,直接返回子组件数据
  if (!parentVal) return childVal
  const ret = {}
  // 合并对象
  extend(ret, parentVal)
  // 遍历子组件数据对象
  for (const key in childVal) {
    // 获取父组件数据对应key的value值
    let parent = ret[key]
    // 获取子组件数据对应key的value值
    const child = childVal[key]
    // 如果父组件value值存在,但不为数组类型;将其设置为数组
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    // 如果父组件数据value存在,则拼接子组件数据value,合并成一个数组;
    // 如果父组件数据value不存在,则判断子组件数据value是否为数组类型,若不是则将其设置为数组
    ret[key] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child]
  }
  return ret
}
// Firefox has a "watch" function on Object.prototype...
// nativeWatch = undefined
export const nativeWatch = ({}).watch
function assertObjectType (name: string, value: any, vm: ?Component) {
  if (!isPlainObject(value)) {
    warn(
      `Invalid value for option "${name}": expected an Object, ` +
      `but got ${toRawType(value)}.`,
      vm
    )
  }
}

其他生命周期钩子
源码解读:
如果父子组件数据只有一个存在,则返回该数据,并以数组的形式;
如果父子组件数据同时存在,则合并两个数据,以数组的形式;父组件数据在前,子组件数据在后;
依次执行父子组件数据。

// 生命周期函数
var LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
];

/**
 * Hooks and props are merged as arrays.
 */
// 如果子组件数据不存在,则返回父组件数据。
// 否则,如果父组件不存在则返回子组件数据;否则合并父子组件数据。
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(function (hook) {
  strats[hook] = mergeHook;
});

Vue2.x选项合并策略二(自定义选项合并策略和生命周期函数)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值