mutrait
(formerly mixwith.js)
A simple and powerful trait library for ES6+.
mutrait
uses a subclass factory strategy to introduce traits into classes (also known as "mixins"), along with some nice syntax sugar.
It allows classes to override methods coming from not only superclasses, but also traits, and it allows traits to override methods from supertraits.
DEPRECATION NOTE: Since the time this package was created, SciSpike has been acquired by Northscaler. There will be no further development on this module. Instead, development will continue at @northscaler/mutrait. You can see all of Northscaler's public Node.js modules at https://www.npmjs.com/search?q=%40northscaler.
Hello, World!
const Trait trait = // 0 const CanSayHello = CanSayHello {} // 2 const person = console // 3// logs 'Hello, world'
0: Uses Trait
& trait
from mutrait
1: Defines a new trait that imparts a sayHello
method.
2: Defines a class that expresses the CanSayHello
trait and doesn't extend anything.
3: Invokes the sayHello
method provided by the CanSayHello
trait.
{ return 'nuqneH!' // 4 }console// logs 'nuqneH!'
4: Demonstrates that classes can override methods provided by traits.
Expressing Multiple Traits
const Trait traits = // 0 const CanSayHello = const CanSayGoodbye = CanSayHello CanSayGoodbye {} // 3 const person = console // 4// logs 'Hello, world'console // 5// logs 'Goodbye, world'
0: Uses Trait
& traits
from mutrait
1: Defines a trait that imparts a sayHello
method
2: Defines a trait that imparts a sayGoodbye
method
3: Defines a class that expresses the CanSayHello
& CanSayGoodbye
traits and doesn't extend anything.
4: Invokes the sayHello
method provided by CanSayHello
.
5: Invokes the sayGoodbye
method provided by CanSayGoodbye
.
More Realistic Example
const Trait trait = const Nameable = Nameable { if !it throw 'nothing given' return it } { if !it throw 'nothing given' return it } const first = 'Cheeky'const last = 'Monkey'const me = mefirstName = firstmelastName = lastassertassertassertassertassert assertassert
Features
super
Just Works™.instanceof
Just Works™ with classes and traits.- Traits can have constructors and instance methods & fields that are accessible to any class or trait involved.
Syntax Sugar
mutrait
provides helpers Trait
, superclass
, traits
, trait
& expressing
that ease in readability in various cases:
- Use
Trait
to define a trait:
const MyTrait =
- Use
trait
when your class declares no superclass and expresses a single trait:
MyTrait {}
- Use
traits
when your class declares no superclass and expresses a multiple traits:
MyTrait MyOtherTrait {}
NOTE:
traits
&trait
are the same function. They're provided simply for readability's sake. Use whichever reads better for you.
- Use
superclass().expressing()
when your class declares a superclass and expresses one or more traits:
MySuper {}
Advantages of subclass factory-based traits over typical JavaScript mixins
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 traits without being constrained to a single inheritance hierarchy, and without monkey-patching or copying.
Method overriding that just works
Methods in subclasses can naturally override methods in the trait or superclass, and traits can override methods in the superclass or supertraits. This means that precedence is preserved - the order is: subclass -> trait__1 -> ... -> trait__N -> superclass.
super
works
Subclasses and traits can use super
normally, as defined in standard Javascript, and without needing the trait library to do special chaining of functions.
Traits can have constructors
Since super()
works, traits can define constructors.
Combined with ES6 rest arguments and the spread operator, traits can have generic constructors that work with any superconstructor by passing along all arguments.
Prototypes and instances are not mutated
Typical JavaScript mixins usually 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, leaving the original superclass intact.
Usage
Defining Traits
The Trait
decorator function wraps a plain subclass factory to add deduplication, caching and instanceof
support:
const Trait = const MyTrait =
Traits defined with the mutrait
decorators do not require any helpers to use.
They still work like plain subclass factories.
Using Traits
Classes use traits in their extends
clause.
Classes that use traits can define and override constructors, methods & fields as usual.
MySuperClass { supera b; // calls MyTrait(a, b) } { console; super; // calls MyTrait.foo() }
Subtraits
There may be time when you have a trait that requires other traits; this can be considering a subtrait
.
This is achieved by having a trait subclass a given class that expresses all required supertraits.
The pattern for that follows.
const Supertrait1 = const Supertrait2 = const Subtrait = Subtrait // 4 { return 'snafu from C' } const c = assert // 5assertassertassertassertassert
1: Some conventional trait.
2: Another conventional trait.
3: Pattern that illustrates a subtrait that requires the two supertraits.
The order of overriding is "last one wins".
In this case, C
overrides Subtrait
overrides Subtrait2
overrides Subtrait1
.
Credits
Credit is most certainly due to mixwith.js for wrapping such a nice bow around mixins. It appeared to be an unmaintained project, so we copied it & created this one.
mutrait
is largely just a renaming from "mixin" to "trait", with some minor adjustments & bugfixes here & there, plus it's managed under a minor-release-per-branch strategy.