# 面型对象-Mixin 模式

在 JavaScript 中,我们只能继承单个对象。每个对象只能有一个 [[Prototype]]。并且每个类只可以扩展另外一个类。

例如,我们有一个 User 类和一个 EventEmitter 类来实现事件生成(event generation),并且我们想将 EventEmitter 的功能添加到 User 中,以便我们的用户可以触发事件(emit event)。

有一个概念可以帮助我们,叫做 “mixins”。

根据维基百科的定义,mixin 是一个包含可被其他类使用而无需继承的方法的类。

换句话说,mixin 提供了实现特定行为的方法,但是我们不单独使用它,而是使用它来将这些行为添加到其他类中。

# 一个 Mixin 实例

在 JavaScript 中构造一个 mixin 最简单的方式就是构造一个拥有实用方法的对象,以便我们可以轻松地将这些实用的方法合并到任何类的原型中。

// mixin
let sayHiMixin = {
  sayHi() {
    alert(`Hello ${this.name}`);
  },
  sayBye() {
    alert(`Bye ${this.name}`);
  }
};

// 用法:
class User {
  constructor(name) {
    this.name = name;
  }
}

// 拷贝方法
Object.assign(User.prototype, sayHiMixin);

// 现在 User 可以打招呼了
new User("Dude").sayHi(); // Hello Dude!

这里没有继承,只有一个简单的方法拷贝。所以 User 可以从另一个类继承,还可以包括 mixin 来 "mix-in“ 其它方法,就像这样:

class User extends Person {
  // ...
}

Object.assign(User.prototype, sayHiMixin);

与此同时,Mixin 可以在自己内部使用继承。

# EventMixin

现在让我们为实际运用构造一个 mixin。

例如,许多浏览器对象的一个重要功能是它们可以生成事件。事件是向任何有需要的人“广播信息”的好方法。因此,让我们构造一个 mixin,使我们能够轻松地将与事件相关的函数添加到任意 class/object 中。

let eventMixin = {
  on(eventName, handler) {
    if (!this._eventHandlers) this._eventHandlers = {};
    if (!this._eventHandlers[eventName]) {
      this._eventHandlers[eventName] = [];
    }
    this._eventHandlers[eventName].push(handler);
  },

  off(eventName, handler) {
    let handlers = this._eventHandlers?.[eventName];
    if (!handlers) return;
    for (let i = 0; i < handlers.length; i++) {
      if (handlers[i] === handler) {
        handlers.splice(i--, 1);
      }
    }
  },

  trigger(eventName, ...args) {
    if (!this._eventHandlers?.[eventName]) {
      return; // 该事件名称没有对应的事件处理程序(handler)
    }

    // 调用事件处理程序(handler)
    this._eventHandlers[eventName].forEach((handler) => handler.apply(this, args));
  }
};
// 创建一个 class
class Menu {
  choose(value) {
    this.trigger("select", value);
  }
}
// 添加带有事件相关方法的 mixin
Object.assign(Menu.prototype, eventMixin);

let menu = new Menu();

// 添加一个事件处理程序(handler),在被选择时被调用:
menu.on("select", (value) => alert(`Value selected: ${value}`));

// 触发事件 => 运行上述的事件处理程序(handler)并显示:
menu.choose("123"); // Value selected: 123

# 总结

Mixin — 是一个通用的面向对象编程术语:一个包含其他类的方法的类。

一些其它编程语言允许多重继承。JavaScript 不支持多重继承,但是可以通过将方法拷贝到原型中来实现 mixin。

我们可以使用 mixin 作为一种通过添加多种行为(例如上文中所提到的事件处理)来扩充类的方法。

如果 Mixins 意外覆盖了现有类的方法,那么它们可能会成为一个冲突点。因此,通常应该仔细考虑 mixin 的命名方法,以最大程度地降低发生这种冲突的可能性。