A simple and powerful mixin library for ES6.
mixwith
differs from other mixin approaches because it does not copy properties from one object to another. Instead, mixwith
works with "subclass factories" which create a new class that extends a superclass with the mixin - this is called a mixin application.
my-mixin.js:
let MyMixin = (superclass) => class extends superclass {
// mixin methods here
};
my-class.js
class MyClass extends MyMixin(MySuperClass) {
// class methods here, go ahead, use super!
}
The subclass factory pattern does not require any support from a library. It's just a natural use of JavaScript class expressions. mixwith.js provides a few helpers that make mixins a little more powerful and easier to use.
mixwith.js makes some use cases very easy:
- Determine if an object or class has had a particular mixin applied to it.
- Cache mixin applications so that a mixin repeatedly applied to the same superclass reuses its resulting subclass.
- De-duplicate mixin application so that including a mixin multiple times in a class hierarchy only applies it once to the prototype type chain.
- Add
instanceof
support to a mixin function.
mixwith.js also provides a little bit of sugar with the mix()
function that makes applying mixins read a little more naturally:
class MyClass extends mix(MySuperClass).with(MyMixin, OtherMixin) {
// class methods here, go ahead, use super!
}
Subclass factory style mixins preserve the object-oriented inheritance properties that classes provide, like method overriding and super
calls, while letting you compose classes out of mixins without being constrained to a single inheritance hierarchy, and without monkey-patching or copying.
Methods in subclasses can naturally override methods in the mixin or superclass, and mixins override methods in the superclass. This means that precedence is preserved - the order is: subclass -> mixin__1 -> ... -> mixin__N -> superclass.
Subclasses and mixins can use super
normally, as defined in standard Javascript, and without needing the mixin library to do special chaining of functions.
Since super()
works, mixins can define constructors. Combined with ES6 rest arguments and the spread operator, mixins can have generic constructors that work with any super constructor by passing along all arguments.
Typical JavaScript mixins usually used to either mutate each instance as created, which can be bad for performance and maintainability, or modify a prototype, which means every object inheriting from that prototype gets the mixin. Subclass factories don't mutate objects, they define new classes to subclass, leaving the original superclass intact.
The Mixin
decorator function wraps a plain subclass factory to add deduplication, caching and instanceof
support:
let MyMixin = Mixin((superclass) => class extends superclass {
constructor(args...) {
// mixins should either 1) not define a constructor, 2) require a specific
// constructor signature, or 3) pass along all arguments.
super(...args);
}
foo() {
console.log('foo from MyMixin');
// this will call superclass.foo()
super.foo();
}
});
Mixins defined with the mixwith.js decorators do not require any helpers to use, they still work like plain subclass factories.
Classes use mixins in their extends
clause. Classes that use mixins can define and override constructors and methods as usual.
class MyClass extends mix(MySuperClass).with(MyMixin) {
constructor(a, b) {
super(a, b); // calls MyMixin(a, b)
}
foo() {
console.log('foo from MyClass');
super.foo(); // calls MyMixin.foo()
}
}
Applies mixin
to superclass
.
apply
stores a reference from the mixin application to the unwrapped mixin
to make isApplicationOf
and hasMixin
work.
This function is usefull for mixin wrappers that want to automatically enable hasMixin support.
Kind: global function
Returns: function
- A subclass of superclass
produced by mixin
Param | Type | Description |
---|---|---|
superclass | function |
A class or constructor function |
mixin | MixinFunction |
The mixin to apply |
Example
const Applier = (mixin) => wrap(mixin, (superclass) => apply(superclass, mixin));
// M now works with `hasMixin` and `isApplicationOf`
const M = Applier((superclass) => class extends superclass {});
class C extends M(Object) {}
let i = new C();
hasMixin(i, M); // true
Returns true
iff proto
is a prototype created by the application of
mixin
to a superclass.
isApplicationOf
works by checking that proto
has a reference to mixin
as created by apply
.
Kind: global function
Returns: boolean
- whether proto
is a prototype created by the application of
mixin
to a superclass
Param | Type | Description |
---|---|---|
proto | Object |
A prototype object created by apply. |
mixin | MixinFunction |
A mixin function used with apply. |
Returns true
iff o
has an application of mixin
on its prototype
chain.
Kind: global function
Returns: boolean
- whether o
has an application of mixin
on its prototype
chain
Param | Type | Description |
---|---|---|
o | Object |
An object |
mixin | MixinFunction |
A mixin applied with apply |
wrap(mixin, wrapper) ⇒ MixinFunction
Sets up the function mixin
to be wrapped by the function wrapper
, while
allowing properties on mixin
to be available via wrapper
, and allowing
wrapper
to be unwrapped to get to the original function.
wrap
does two things:
- Sets the prototype of
mixin
towrapper
so that properties set onmixin
inherited bywrapper
. - Sets a special property on
mixin
that points back tomixin
so that it can be retreived fromwrapper
Kind: global function
Returns: MixinFunction
- wrapper
Param | Type | Description |
---|---|---|
mixin | MixinFunction |
A mixin function |
wrapper | MixinFunction |
A function that wraps mixin |
unwrap(wrapper) ⇒ MixinFunction
Unwraps the function wrapper
to return the original function wrapped by
one or more calls to wrap
. Returns wrapper
if it's not a wrapped
function.
Kind: global function
Returns: MixinFunction
- The originally wrapped mixin
Param | Type | Description |
---|---|---|
wrapper | MixinFunction |
A wrapped mixin produced by wrap |
Cached(mixin) ⇒ MixinFunction
Decorates mixin
so that it caches its applications. When applied multiple
times to the same superclass, mixin
will only create one subclass, memoize
it and return it for each application.
Note: If mixin
somehow stores properties its classes constructor (static
properties), or on its classes prototype, it will be shared across all
applications of mixin
to a super class. It's reccomended that mixin
only
access instance state.
Kind: global function
Returns: MixinFunction
- a new mixin function
Param | Type | Description |
---|---|---|
mixin | MixinFunction |
The mixin to wrap with caching behavior |
DeDupe(mixin) ⇒ MixinFunction
Decorates mixin
so that it only applies if it's not already on the
prototype chain.
Kind: global function
Returns: MixinFunction
- a new mixin function
Param | Type | Description |
---|---|---|
mixin | MixinFunction |
The mixin to wrap with deduplication behavior |
HasInstance(mixin) ⇒ MixinFunction
Adds [Symbol.hasInstance] (ES2015 custom instanceof support) to mixin
.
Kind: global function
Returns: MixinFunction
- the given mixin function
Param | Type | Description |
---|---|---|
mixin | MixinFunction |
The mixin to add [Symbol.hasInstance] to |
BareMixin(mixin) ⇒ MixinFunction
A basic mixin decorator that applies the mixin with apply so that it can be used with isApplicationOf, hasMixin and the other mixin decorator functions.
Kind: global function
Returns: MixinFunction
- a new mixin function
Param | Type | Description |
---|---|---|
mixin | MixinFunction |
The mixin to wrap |
Mixin(mixin) ⇒ MixinFunction
Decorates a mixin function to add deduplication, application caching and instanceof support.
Kind: global function
Returns: MixinFunction
- a new mixin function
Param | Type | Description |
---|---|---|
mixin | MixinFunction |
The mixin to wrap |
A fluent interface to apply a list of mixins to a superclass.
class X extends mix(Object).with(A, B, C) {}
The mixins are applied in order to the superclass, so the prototype chain will be: X->C'->B'->A'->Object.
This is purely a convenience function. The above example is equivalent to:
class X extends C(B(A(Object))) {}
Kind: global function
Param | Type | Default |
---|---|---|
[superclass] | function |
Object |
A function that returns a subclass of its argument.
Kind: global typedef
Returns: function
- A subclass of superclass
Param | Type |
---|---|
superclass | function |
Example
const M = (superclass) => class extends superclass {
getMessage() {
return "Hello";
}
}