Vue.mixin源码解析

146 阅读2分钟

官方说明:

  • 参数
    • {Object} mixin
  • 用法:全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。插件作者可以使用混入,向组件注入自定义的行为。不推荐在应用代码中使用

源码解析(Vue-v2.7.14)

// src/core/global-api/mixin.ts
// Vue的extend 和 mixin 属性是使用这个方法实现合并。
export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    //由ASSET_TYPES可知,定义初始化options的值 = ['component', 'directive', 'filter']
    // 返回一个合并后对象
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

//src/core/global-api/index.ts
//定义Vue的options
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

//src/shared/constants.ts
//定义ASSWT_TYPES的值的类型
export const ASSET_TYPES = ['component', 'directive', 'filter'] as const

mergeOptions方法解析

// src/core/util/options.ts 
//合并Options
export function mergeOptions(
  parent: Record<string, any>,
  child: Record<string, any>,
  vm?: Component | null
): ComponentOptions {
  if (__DEV__) {
    checkComponents(child)
  }

  if (isFunction(child)) {
    // @ts-expect-error
    child = child.options
  }
	//处理Props,inject,directives,保证符合Vue规范
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if (!child._base) {
    if (child.extends) {
      // extends也是调用此方法进行合并
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      //child含有mixins时,遍历递归合并
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

  const options: ComponentOptions = {} as any
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }

  //合并属性
  function mergeField(key: any) {
    //获取strats方法(策略模式),不同的optins采取不同的合并模式
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

strats策略解析

// 根据不同的options 进行不同的合并策略处理
// src/core/util/options.ts 
/**
 * 默认策略。
 * 没有childVal值,则返回parentVal
 */
const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined ? parentVal : childVal
}


function mergeAssets(
  parentVal: Object | null,
  childVal: Object | null,
  vm: Component | null,
  key: string
): Object {
  //用parentVal为原型创建一个对象
  const res = Object.create(parentVal || null)
  //如果有childVal,则赋值覆盖原来的属性
  if (childVal) {
    __DEV__ && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}
//['component', 'directive', 'filter']的合并策略
ASSET_TYPES.forEach(function (type) {
  strats[type + 's'] = mergeAssets
})

/**
 * props methods inject computed的合并策略-子覆盖父
 */
strats.props =
  strats.methods =
  strats.inject =
  strats.computed =
    function (
      parentVal: Object | null,
      childVal: Object | null,
      vm: Component | null,
      key: string
    ): Object | null {
      if (childVal && __DEV__) {
        assertObjectType(key, childVal, vm)
      }
      if (!parentVal) return childVal
      const ret = Object.create(null)
      //将parent对象的属性合并到ret对象
      extend(ret, parentVal)
      //如果存在childVal,将childVal对象的属性合并到ret,会覆盖之前的parent属性
      if (childVal) extend(ret, childVal)
      return ret
    }
// provide的合并策略-合并赋值
strats.provide = function (parentVal: Object | null, childVal: Object | null) {
  if (!parentVal) return childVal
  return function () {
    const ret = Object.create(null)
    //先合并父。将parentVal的值赋给ret
    mergeData(ret, isFunction(parentVal) ? parentVal.call(this) : parentVal)
    //再合并子,将childVal的值赋给ret
    if (childVal) {
      mergeData(
        ret,
        isFunction(childVal) ? childVal.call(this) : childVal,
        false // non-recursive
      )
    }
    return ret
  }
}
// data的合并策略-重新赋值
strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): Function | null {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      __DEV__ &&
        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)
}
/**
 * watch的合并策略
 *watch 不会被覆盖,会作为一个两数组进行合并
 */
strats.watch = function (
  parentVal: Record<string, any> | null,
  childVal: Record<string, any> | null,
  vm: Component | null,
  key: string
): Object | null {
  // work around Firefox's Object.prototype.watch...
  //@ts-expect-error work around
  if (parentVal === nativeWatch) parentVal = undefined
  //@ts-expect-error work around
  if (childVal === nativeWatch) childVal = undefined
  /* istanbul ignore if */
  if (!childVal) return Object.create(parentVal || null)
  if (__DEV__) {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret: Record<string, any> = {}
  extend(ret, parentVal)
  //遍历childVal属性
  for (const key in childVal) {
    let parent = ret[key]
    const child = childVal[key]
    if (parent && !isArray(parent)) {
      parent = [parent]
    }
    //合并父和子watch的数组
    ret[key] = parent ? parent.concat(child) : isArray(child) ? child : [child]
  }
  return ret
}

//生命周期的合并策略
LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeLifecycleHook
})

/**
 * 用数组的方式混入
 */
export function mergeLifecycleHook(
  parentVal: Array<Function> | null,
  childVal: Function | Array<Function> | null
): Array<Function> | null {
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : isArray(childVal)
      ? childVal
      : [childVal]
    : parentVal
  return res ? dedupeHooks(res) : res
}

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

strats的工具方法解析

// src/core/util/options.ts 
// data合并的策略的工具方法
export function mergeDataOrFn(
  parentVal: any,
  childVal: any,
  vm?: Component
): Function | null {
  //没有传入vm实例
  if (!vm) {
    // in a Vue.extend merge, both should be functions
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    return function mergedDataFn() {
      // 如果data是个函数,执行获取data对象
      return mergeData(
        isFunction(childVal) ? childVal.call(this, this) : childVal,
        isFunction(parentVal) ? parentVal.call(this, this) : parentVal
      )
    }
  //有传入vm实例
  } else {
    return function mergedInstanceDataFn() {
      // 获取data对象或者执行data()方法获取data对象
      const instanceData = isFunction(childVal)
        ? childVal.call(vm, vm)
        : childVal
      const defaultData = isFunction(parentVal)
        ? parentVal.call(vm, vm)
        : parentVal
      if (instanceData) {
        //合并,重新赋值覆盖子data中的数据,返回覆盖后的instanceData
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

/**
 * 递归合并两个对象
 */
function mergeData(
  to: Record<string | symbol, any>,
  from: Record<string | symbol, any> | null,
  recursive = true
): Record<PropertyKey, any> {
  if (!from) return to
  let key, toVal, fromVal

  const keys = hasSymbol
    //静态方法 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组。
    ? (Reflect.ownKeys(from) as string[])
    : Object.keys(from)

  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    // 已经是响应,不处理
    if (key === '__ob__') continue
    toVal = to[key]
    fromVal = from[key]
    if (!recursive || !hasOwn(to, key)) {
      // 重新设置
      set(to, key, fromVal)
    } else if (
      toVal !== fromVal &&
      //判断是否是对象
      isPlainObject(toVal) &&
      isPlainObject(fromVal)
    ) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}
// src/shared/util.ts 
/**
 * 添加属性到目标对象
 */
export function extend(
  to: Record<PropertyKey, any>,
  _from?: Record<PropertyKey, any>
): Record<PropertyKey, any> {
  for (const key in _from) {
    to[key] = _from[key]
  }
  return to
}

总结

  1. Vue.mixin主要是使用mergeOptions方法,将options和mixin的对象进行合并。通过不同的策略对不同的属性采取不同的合并方式
  2. component、directives、filters通过Object.create(ParentVal)创建新对象。再通过extend(childVal)进行属性的覆盖。即原型链调用的方式完成。
  3. 对于生命周期LIFECYCLE_HOOKSwatch。 通过父子数组合并完成
  4. data则是通过,set方法相同属性重新赋值
  5. props methods inject computed也是使用新参数代替旧参数