资讯专栏INFORMATION COLUMN

JS设计模式之Mixin(混入)模式

caiyongji / 3317人阅读

摘要:概念模式就是一些提供能够被一个或者一组子类简单继承功能的类意在重用其功能。示例下面通过一个简单的例子来演示这个模式混入模式的实现不指定特定方法名的时候,将后者所有的方法都添加到前者里优缺点优点有助于减少系统中的重复功能及增加函数复用。

概念

Mixin模式就是一些提供能够被一个或者一组子类简单继承功能的类,意在重用其功能。在面向对象的语言中,我们会通过接口继承的方式来实现功能的复用。但是在javascript中,我们没办法通过接口继承的方式,但是我们可以通过javascript特有的原型链属性,将功能引用复制到原型链上,达到功能的注入。

示例

下面通过一个简单的例子来演示这个模式

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中维持这种共享功能并专注于仅实现系统中真正不同的功能,来轻松避免任何重复。

缺点
有些人认为将功能注入对象原型中会导致原型污染和函数起源方面的不确定性。

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的分支

export function initMixin (Vue: Class) {
  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上面

/**
 * 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(混入)模式

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103895.html

相关文章

  • mixin混入逻辑

    摘要:现在有一个需求,将没有而有的属性混入到中。有而且也有的属性不混入以为准。通过这种混入机制滚雪球的方式可以不断地扩充一个对象地的功能。暂且将它定义为混入模式。混入模式是构建复杂对象的一种常用的模式。 有A对象和B对象,A, B对象有有方法属性和数据属性。现在有一个需求,将A没有而B有的属性混入到A中。A有而且B也有的属性不混入以A为准。通过这种混入机制(滚雪球的方式)可以不断地扩充一个对...

    hufeng 评论0 收藏0
  • 高级 Vue 组件模式 (4)

    摘要:使用替换目标在第三篇文章中,我们使用来抽离了注入依赖项的公共逻辑。成果通过作用域插槽,我们有效地避免了第三方组件由于混入而可能造成的命名冲突以及隐式依赖等问题。 04 使用 slot 替换 mixin 目标 在第三篇文章中,我们使用 mixin 来抽离了注入 toggle 依赖项的公共逻辑。在 react 中,类似的需求是通过 HOC 的方式来解决的,但是仔细想想的话,react 在早...

    LancerComet 评论0 收藏0
  • 高级 Vue 组件模式 (3)

    摘要:在中,我们是否也有一些手段或特性来提高组件的复用程度和灵活性呢答案当然是有的,那就是。成果通过实现,我们成功将注入的逻辑抽离了出来,这样每次需要共享组件的状态和方法时,混入该即可。 03 使用 mixin 来增强 Vue 组件 目标 之前一篇文章中,我们虽然将 toggle 组件划分为了 toggle-button、toggle-on 和 toggle-off 三个子组件,且一切运行良...

    iKcamp 评论0 收藏0
  • 从装饰模式到装饰器

    摘要:从装饰模式到装饰器装饰模式装饰模式的作用是在不修改原有的接口的情况下,让类表现的更好。它是一个语法糖说完了装饰模式,我们再看一下在中最新引入的装饰器。 从装饰模式到装饰器 装饰模式 装饰模式的作用是:在不修改原有的接口的情况下,让类表现的更好。 什么叫更好? 为什么需要装饰模式 自然是继承有一些问题继承会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之改变; 超类的内...

    monw3c 评论0 收藏0
  • 如何正确的(?)利用 Vue.mixin() 偷懒

    摘要:前言最近开发的页面以及功能大都以表格为主,接口获取来的数据大都是需要经过处理,比如时间戳需要转换,或者状态码的转义。首先,还是在文件中定义一个状态码对应对象,这里我们将其对应的内容设为段落。 前言 最近开发的页面以及功能大都以表格为主,接口获取来的 JSON 数据大都是需要经过处理,比如时间戳需要转换,或者状态码的转义。对于这样的问题,各大主流框架都提供了类似于过滤的方法,在 Vue ...

    Leo_chen 评论0 收藏0

发表评论

0条评论

caiyongji

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<