JS設計模式之Mixin(混入)模式

概念

Mixin模式就是一些提供可以被一個或者一組子類簡單繼承功能的類,意在重用其功能。在面向對象的語言中,咱們會經過接口繼承的方式來實現功能的複用。可是在javascript中,咱們沒辦法經過接口繼承的方式,可是咱們能夠經過javascript特有的原型鏈屬性,將功能引用複製到原型鏈上,達到功能的注入。javascript

示例

下面經過一個簡單的例子來演示這個模式vue

var Car = function(settings) {
  this.model = settings.model || "no model provided"
  this.color = settings.color || "no color provided"
}

var Mixin = function() {}

Mixin.prototype = {
  driveForward: function() {
    console.log("drive forward")
  },
  driveBackward: function() {
    console.log("drive backward")
  },
  driveSideways: function() {
    console.log("drive sideways")
  }
}

//混入模式的實現
function Merge(recClass, giveClass) {
  if(arguments.length > 2) {
    for(var i = 2, lenth = arguments.length; i < lenth ; ++ i) {
      var methodName = arguments[i]
      recClass.prototype[methodName] = giveClass.prototype[methodName]
    }
  }else {
    for(var methodName in giveClass.prototype) {
      if(!recClass.prototype[methodName]) {
        recClass.prototype[methodName] = giveClass.prototype[methodName]
      }
    }
  }
}

Merge(Car, Mixin, "driveForward", "driveBackward")

var myCar = new Car({
  model: "BMW",
  color: "blue"
})

myCar.driveForward()    //drive forward
myCar.driveBackward()   //drive backward

//不指定特定方法名的時候,將後者全部的方法都添加到前者裏
Merge(Car, Mixin)

var mySportsCar = new Car({
  model: "Porsche",
  color: "red"
})

mySportsCar.driveForward()  //drive forward

優缺點

優勢
有助於減小系統中的重複功能及增長函數複用。當一個應用程序可能須要在各類對象實例中共享行爲時,咱們能夠經過在Mixin中維持這種共享功能並專一於僅實現系統中真正不一樣的功能,來輕鬆避免任何重複。java

缺點
有些人認爲將功能注入對象原型中會致使原型污染和函數起源方面的不肯定性。segmentfault

Vue混入功能研究

vue中關於混入的代碼目錄/src/core/global-api/mixin.js
能夠看到vue源碼中是經過mergeOptions來合併配置到options上設計模式

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

下面咱們來看合併配置的相關代碼/src/core/instance/init.js
當執行new Vue的時候options._isComponent爲false,會走else的分支api

export function initMixin (Vue: Class<Component>) {
  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 {
      vm.$options = mergeOptions(  //合併配置
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    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的實現src/core/util/options.js
mergeField將this.options(parent)和須要混入的對象mixin(child)合併在this.options上面ide

/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

參考

《JavaScript設計模式》函數

JS設計模式系列文章

JS設計模式之Obeserver(觀察者)模式、Publish/Subscribe(發佈/訂閱)模式
JS設計模式之Factory(工廠)模式
JS設計模式之Singleton(單例)模式
JS設計模式之Facade(外觀)模式
JS設計模式之Module(模塊)模式、Revealing Module(揭示模塊)模式
JS設計模式之Mixin(混入)模式ui

相關文章
相關標籤/搜索