Mixin

在装饰器的基础上,可以实现Mixin模式。所谓Mixin模式,就是对象继承的一种替代方案,中文译为“混入”(mix in),意为在一个对象之中混入另外一个对象的方法。

请看下面的例子。

  1. const Foo = {
  2. foo() { console.log('foo') }
  3. };
  4. class MyClass {}
  5. Object.assign(MyClass.prototype, Foo);
  6. let obj = new MyClass();
  7. obj.foo() // 'foo'

上面代码之中,对象Foo有一个foo方法,通过Object.assign方法,可以将foo方法“混入”MyClass类,导致MyClass的实例obj对象都具有foo方法。这就是“混入”模式的一个简单实现。

下面,我们部署一个通用脚本mixins.js,将 Mixin 写成一个装饰器。

  1. export function mixins(...list) {
  2. return function (target) {
  3. Object.assign(target.prototype, ...list);
  4. };
  5. }

然后,就可以使用上面这个装饰器,为类“混入”各种方法。

  1. import { mixins } from './mixins';
  2. const Foo = {
  3. foo() { console.log('foo') }
  4. };
  5. @mixins(Foo)
  6. class MyClass {}
  7. let obj = new MyClass();
  8. obj.foo() // "foo"

通过mixins这个装饰器,实现了在MyClass类上面“混入”Foo对象的foo方法。

不过,上面的方法会改写MyClass类的prototype对象,如果不喜欢这一点,也可以通过类的继承实现 Mixin。

  1. class MyClass extends MyBaseClass {
  2. /* ... */
  3. }

上面代码中,MyClass继承了MyBaseClass。如果我们想在MyClass里面“混入”一个foo方法,一个办法是在MyClassMyBaseClass之间插入一个混入类,这个类具有foo方法,并且继承了MyBaseClass的所有方法,然后MyClass再继承这个类。

  1. let MyMixin = (superclass) => class extends superclass {
  2. foo() {
  3. console.log('foo from MyMixin');
  4. }
  5. };

上面代码中,MyMixin是一个混入类生成器,接受superclass作为参数,然后返回一个继承superclass的子类,该子类包含一个foo方法。

接着,目标类再去继承这个混入类,就达到了“混入”foo方法的目的。

  1. class MyClass extends MyMixin(MyBaseClass) {
  2. /* ... */
  3. }
  4. let c = new MyClass();
  5. c.foo(); // "foo from MyMixin"

如果需要“混入”多个方法,就生成多个混入类。

  1. class MyClass extends Mixin1(Mixin2(MyBaseClass)) {
  2. /* ... */
  3. }

这种写法的一个好处,是可以调用super,因此可以避免在“混入”过程中覆盖父类的同名方法。

  1. let Mixin1 = (superclass) => class extends superclass {
  2. foo() {
  3. console.log('foo from Mixin1');
  4. if (super.foo) super.foo();
  5. }
  6. };
  7. let Mixin2 = (superclass) => class extends superclass {
  8. foo() {
  9. console.log('foo from Mixin2');
  10. if (super.foo) super.foo();
  11. }
  12. };
  13. class S {
  14. foo() {
  15. console.log('foo from S');
  16. }
  17. }
  18. class C extends Mixin1(Mixin2(S)) {
  19. foo() {
  20. console.log('foo from C');
  21. super.foo();
  22. }
  23. }

上面代码中,每一次混入发生时,都调用了父类的super.foo方法,导致父类的同名方法没有被覆盖,行为被保留了下来。

  1. new C().foo()
  2. // foo from C
  3. // foo from Mixin1
  4. // foo from Mixin2
  5. // foo from S