In Object-Oriented Design Patterns, the goal is to eliminate inheritance as much as possible, for inheritance is inflexible and jumbles together unrelated entities. You should program to an interface, not an implementation. Separate what varies from what stays the same. Design Patterns introduces the concept of delegation in the form of the strategy pattern. But due to JavaScript's dynamic nature, we can also easily use the concept of mixins to separate what varies from what stays the same.
Another reason to opt for mixins is that since JavaScript is a prototypal language, it chains objects together via the prototype chain, but if you overuse this feature, you will discover that chaining prototypes is slow in huge chains.
In fact, by using mixins, you can add functionality directly to the prototype of an object rather than chaining prototypes, reducing the expense of the lookup algorithm. So, for example, below we create two singleton objects 'speak' and 'fly'. These are objects that implement a behavior. Different things can speak and fly, but those different things should not inherit from each other. For example, you might have a Mallard Duck and a Parrot.
var speakable = {
speak: function(){ return this.name + " speaks" }
}
var flyable = {
fly: function(){ return this.name + " flies" }
}
In the jQuery world and previous to ES6's clean class syntax, you would add the mixins as follows utilizing constructor functions:
var MallardDuck = function(name){
this.name = name;
}
var Parrot = function(name){
this.name = name;
}
$.extend(MallardDuck.prototype, speakable, flyable);
$.extend(Parrot.prototype, speakable, flyable);
var duck = new MallardDuck('Bob');
var parrot = new Parrot('Jane');
console.log(duck.speak());
console.log(parrot.speak());
Without jQuery and again prior to ES6, if you wanted to extend an object with functionality via a mixin facility, then you could simply write your own extend mixin facility:
function extend(target){
// if no mixin objects are provided, then return out of function
if(!arguments[1])
return;
// the first argument is the target object we want to mix behavior into, so we start our loop at index 1
for(var i = 1; j = arguments.length; i < j; i++) {
// grab the singleton object to mixin into target
var source = arguments[i];
for(var propr in source) {
// ensure we do not override a property that target already has
if(!target[prop] && source.hasOwnProperty(prop)){
target[prop] = source[prop];
}
}
}
}
Rather than relying on an external library like jQuery, which is strongly discouraged for this simple purpose, or writing our own utility mixin facility, we can utilize the Object.assign property available in ES6. Furthermore, we can utilize this within ES6 classes. The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object.
const speakable = {
speak: function(){ return `${this.name} speaks`; }
}
const flyable = {
fly: function(){ return `${this.name} flies` }
}
class MallardDuck {
constructor(name) {
this.name = name;
Object.assign(MallardDuck.prototype, speakable); // add the mixin to prototype chain. Alternatively, use 'this' to add directly to the new object being created
}
}
class Parrot {
constructor(name) {
this.name = name;
Object.assign(Parrot.prototype, speakable); // add the mixin to prototype chain. Alternatively, use 'this' to add directly to the new object being created
}
}
const duck = new MallardDuck('Mallard Duck');
const parrot = new Parrot('Parrot');
console.log(duck.speak());
console.log(parrot.speak());