ClassMix implements a mixin pattern in ~30SLOC that enables classes to extend multiple classes with reasonably simple semantics:
- The mixed class's super-class prototype will look up properties on the mixins' prototypes in mixin order, with the first property found winning.
- The mixed class's super-constructor will look up properties on the mixins' constructors in mixin order, with the first property found winning.
- The mixed class's super-constructor implementation will forward all arguments to all super-constructors.
This library is implemented using Proxies, WeakMaps, Maps, Sets, and a variety of ES6 syntax.
class EventTarget { /* impl of add/removeEventListener */ }
class Disposable { /* impl of dispose() */}
class Element extends mix(EventTarget, Disposable) {
// default constructor forwards arguments, which works in this case.
}
// can specialize Element further with subclasses
class MyElement extends Element { }
// and by adding further mixins
class MyEnumerableElement extends mix(MyElement, Enumerable) { }
class Person() {
constructor(name) { this.name = name }
talk(){};
}
class Claxon {
constructor(volume) {
this.volume = volume;
}
sound(){};
}
class LoudPerson extends mix(Person, Claxon) {
constructor(name, volume) {
// Person and Claxon added to super-class prototype for convenience
super.Person(name);
super.Claxon(volume);
}
}
var bob = new LoudPerson("Bob", 11);
bob.talk(); bob.sound();
class A { a() {} }
class B extends A { b() {} }
class C extends A { c() {} }
class D extends mix(B, C) { d() {} }
var obj = new D();
obj.a(); obj.b(); obj.c(); obj.d(); // all work
- Avoid conflicting names unless you know what you're doing.
- Avoid @@create. First @@create found will be used to allocate the this object, and since most everything will have @@create, it's a best practice to put the class with the @@create behavior you want first in the mix list.
- Classes designed to be mixed into other classes should store private state in a weakmap to avoid conflicts with other mixins.
Unlike mixin approaches that copy properties, this method uses prototypical inheritance and preserves all prototype chains. This means that augmenting a mixin's prototype works identically to, for example, augmenting a built-in prototype.
Very little syntax - just the mix function.
Mixed classes are highly composable. You can mix two classes together, and mix that class with a third. I believe most compositions will work as expected.
No implementation has optimized proxies to a degree that makes this sane. Even a theoretically optimal implementation would incur some overhead in property lookup, which is usually a hot path.