Skip to content

Mixin 模式

Mixin 是一种“按需组合能力”的写法。它不是继承树上的父类,而是把一组可复用方法混入到类或对象中。

为什么会有 Mixin

单继承只能有一个直接父类,但实际开发里,一个类往往可能同时需要多种能力,例如:

  • 可记录日志
  • 可触发事件
  • 可缓存结果
  • 可序列化

这时如果全都靠继承来组织,很容易把继承链拉得很复杂。Mixin 更适合表达“组合能力”。

最基础的写法

最简单的 mixin 就是一个普通对象,里面放一组方法:

js
const sayHiMixin = {
  sayHi() {
    console.log(`Hi, ${this.name}`)
  },
  sayBye() {
    console.log(`Bye, ${this.name}`)
  },
}

然后把它混入类的原型:

js
class User {
  constructor(name) {
    this.name = name
  }
}

Object.assign(User.prototype, sayHiMixin)

const user = new User('Lee')
user.sayHi()

这样 User 本身没有继承 sayHiMixin,但实例依然获得了这些方法。

Mixin 本质上是“组合”,不是“继承”

它和 extends 的区别很重要:

  • extends:建立父类和子类关系。
  • mixin:把一组方法拼进现有类或对象。

也就是说,mixin 更像“能力注入”,而不是“类型层级”。

混入多个能力

Mixin 模式常见于把多个小能力组合到同一个类上:

js
const canLog = {
  log(message) {
    console.log(message)
  },
}

const canSayHi = {
  sayHi() {
    this.log(`Hi, ${this.name}`)
  },
}

class User {
  constructor(name) {
    this.name = name
  }
}

Object.assign(User.prototype, canLog, canSayHi)

带依赖的 mixin

有些 mixin 会依赖类本身已有的字段或方法:

js
const canSayHi = {
  sayHi() {
    console.log(`Hi, ${this.name}`)
  },
}

class User {
  constructor(name) {
    this.name = name
  }
}

这里 mixin 默认假设实例上存在 name。这也是 mixin 的一个风险:它往往依赖隐含约定。

实际场景

Mixin 常见于:

  • 事件发布订阅能力
  • 日志能力
  • 缓存能力
  • 序列化能力
  • 表单、组件、模型上的共用辅助方法

例如一个最小事件能力 mixin:

js
const eventMixin = {
  on(eventName, handler) {
    if (!this._events) {
      this._events = {}
    }

    if (!this._events[eventName]) {
      this._events[eventName] = []
    }

    this._events[eventName].push(handler)
  },

  emit(eventName, ...args) {
    const handlers = this._events?.[eventName] || []
    handlers.forEach((handler) => handler(...args))
  },
}

Mixin 的优点

  • 能复用横切能力。
  • 不强迫建立复杂继承树。
  • 组合起来更灵活。

Mixin 的代价

它虽然灵活,但也有明显边界:

  • 方法来源不如继承直观。
  • 命名冲突更容易发生。
  • 依赖约定时,可维护性会下降。
  • 混入过多时,类的行为来源会变得分散。

例如两个 mixin 提供同名方法时,后面的会覆盖前面的:

js
Object.assign(User.prototype, mixinA, mixinB)

这类冲突如果没有明确规范,后期会很难排查。

和高阶函数 / 组合函数的关系

Mixin 只是“组合能力”的一种老牌写法。在现代代码里,也常看到:

  • 工具函数组合
  • 高阶函数
  • 组合式 API
  • hooks / composables

它们本质上都在解决“能力复用”问题,只是组织方式不同。

使用建议

  • 需要复用一组横切能力时,可以考虑 mixin。
  • 能力边界必须足够小,避免做成“大而全”的万能 mixin。
  • 混入前先确认是否会发生命名冲突。
  • 如果复用逻辑更适合写成独立函数或组合式能力,就不要强行使用 mixin。

基于 MIT 许可发布