淺析vue混入(mixin)

  vue中的混入,能夠在必定程度上提升代碼的複用性。通俗來講,混入相似於「繼承」,當前組件對象繼承於組件對象,通常狀況下遵循「就近原則」。可是與繼承不一樣的是,繼承通常都跟隨着屬性的重寫與合併,混入在不一樣的配置項中,有着不一樣的混入策略,下面會一一進行介紹vue不一樣配置項的混入策略。vue混入的基本流程如圖所示,混入屬性的合併是發生在組件的生命週期鉤子調用以前的。vue

  

   在咱們實例化vue時,主要是調用Vue._init方法,此方法,主要功能是初始化組件狀態、事件,具體代碼以下:ios

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    // 合併屬性,判斷初始化的是不是組件
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      // 合併vue屬性
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      // 初始化proxy攔截器
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 初始化options
    initLifecycle(vm)
    // 初始化組件事件偵聽
    initEvents(vm)
    // 初始化渲染方法
    initRender(vm)
    callHook(vm, 'beforeCreate')
    // 初始化依賴注入內容,在初始化data、props以前
    initInjections(vm) // resolve injections before data/props
    // 初始化props/data/method/watch/methods
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    // 掛載元素
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }

  mergeOptions是合併組件的配置項,第一次實例化Vue時調用,接收兩個參數,第一個參數是構造函數默認自帶的屬性,在項目初始化時會調用initGlobalAPI方法,會在Vue構造函數上初始化一些默認的配置,具體代碼以下所示,第二個爲咱們實例化vue配置的屬性設計模式

// 初始化全局API
export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }
  // 初始化Vue構造函數的options,初始屬性爲components,directives,filters
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  // _base屬性
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)
  // 初始化vue.use api
  initUse(Vue)
  // 初始化mixins api
  initMixin(Vue)
  // 初始化extend api
  initExtend(Vue)
  // 初始化component,directive,filter
  initAssetRegisters(Vue)
}

  混入實現的主要代碼以下:api

 1 export function mergeOptions (
 2   parent: Object,
 3   child: Object,
 4   vm?: Component
 5 ): Object {
 6   if (process.env.NODE_ENV !== 'production') {
 7     // 檢測組件名稱是否合法
 8     checkComponents(child)
 9   }
10 
11   if (typeof child === 'function') {
12     child = child.options
13   }
14   // 格式化屬性名稱
15   normalizeProps(child, vm)
16   // 格式化依賴注入內容
17   normalizeInject(child, vm)
18   // 格式化指令內容
19   normalizeDirectives(child)
20 
21   // Apply extends and mixins on the child options,
22   // but only if it is a raw options object that isn't
23   // the result of another mergeOptions call.
24   // Only merged options has the _base property.
25   // 只有合併的options擁有_base屬性
26   // 須要遞歸進行合併屬性
27   // 首先合併extends和mixins
28   if (!child._base) {
29     if (child.extends) {
30       parent = mergeOptions(parent, child.extends, vm)
31     }
32     if (child.mixins) {
33       for (let i = 0, l = child.mixins.length; i < l; i++) {
34         parent = mergeOptions(parent, child.mixins[i], vm)
35       }
36     }
37   }
38 
39   const options = {}
40   let key
41   for (key in parent) {
42     mergeField(key)
43   }
44   for (key in child) {
45     if (!hasOwn(parent, key)) {
46       mergeField(key)
47     }
48   }
49   // 合併屬性
50   function mergeField (key) {
51     // 獲取屬性的合併策略
52     const strat = strats[key] || defaultStrat
53     // 調用屬性合併策略,返回值爲屬性合併結果
54     options[key] = strat(parent[key], child[key], vm, key)
55   }
56   return options
57 }

  具體某個字段的合併,調用的是mergeField方法,此方法主要是獲取代碼混入策略,返回值做爲混入的結果。經過調試咱們能夠看出,混入的策略對象中包含咱們常見的vue屬性,以下所示:數組

  

   混入的實現,採用了策略模式的設計模式,在對組件數據初始化時,會遍歷組件的配置文件,根據配置文件,調用對應的策略方法。若是組件中存在不是vue指定的配置,就是策略類strats中不包含的屬性,就會調用默認的合併方法defaultStrat,該方法的定義以下:閉包

/**
 * Default strategy.
 * 默認的屬性合併策略,採用就近原則,若是子級沒有,就採用父級的
 */
const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}

  strats是從哪來的呢? 屬性合併策略strats默認會讀取config中的配置文件,在項目初始化時,首先會初始化全局的api,將config文件掛載到Vue構造方法中,再者會初始化全部的屬性合併策略,以下圖所示:less

 

  

    若是咱們要自定義屬性合併策略,只須要覆蓋掉Vue構造方法中的合併策略,也就是全局定義一個合併策略,以下所示:ide

// 自定義屬性合併策略
Vue.config.optionMergeStrategies.methods = function (toVal, fromVal) {
    if (toVal && fromVal) return fromVal
    if (toVal) return toVal
    if (fromVal) return fromVal
}

    vue對組件配置項的每一部分執行的合併策略有所差別,每一項的合併策略,主要分爲覆蓋、保留,以下所示:函數

  

   data的混入返回的是一個function,參數保持對父級的引用,在初始化state的時候會調用,data的混入策略以下:ui

// 定義data的屬性合併策略
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
 * 合併對象 or 數組
 * 利用閉包,返回一個包含私有參數的執行方法
 */
export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    // in a Vue.extend merge, both should be functions
    // 在Vue.extend場景中???
    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
      }
    }
  }
}
相關文章
相關標籤/搜索