zoukankan      html  css  js  c++  java
  • 浅析vue混入(mixin)

      vue中的混入,可以在一定程度上提高代码的复用性。通俗来说,混入类似于“继承”,当前组件对象继承于组件对象,一般情况下遵循“就近原则”。但是与继承不同的是,继承一般都跟随着属性的重写与合并,混入在不同的配置项中,有着不同的混入策略,下面会一一进行介绍vue不同配置项的混入策略。vue混入的基本流程如图所示,混入属性的合并是发生在组件的生命周期钩子调用之前的。

      

       在我们实例化vue时,主要是调用Vue._init方法,此方法,主要功能是初始化组件状态、事件,具体代码如下:

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

      混入实现的主要代码如下:

     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构造方法中,再者会初始化所有的属性合并策略,如下图所示:

     

      

        如果我们要自定义属性合并策略,只需要覆盖掉Vue构造方法中的合并策略,也就是全局定义一个合并策略,如下所示:

    // 自定义属性合并策略
    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的混入策略如下:

    // 定义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
          }
        }
      }
    }

       

  • 相关阅读:
    并发系列64章(异步编程三)第四章
    并发系列64章(异步编程二)第三章
    并发系列64章(异步编程)第二章
    sql 语句系列(记录时间差)[八百章之第十八章]
    sql 语句系列(两个日期之间)[八百章之第十七章]
    sql 语句系列(计算的进阶)[八百章之第十六章]
    sql 语句系列(众数中位数与百分比)[八百章之第十五章]
    [转]迅速提升三倍你的工作效率
    漫谈碎片化学习(Fragmentation learning)
    [转]禅修程序员十诫
  • 原文地址:https://www.cnblogs.com/gerry2019/p/11889050.html
Copyright © 2011-2022 走看看