Mixins
JavaScript’s object mechanism does not automatically perform copy behavior when you “inherit” or “instantiate”. Plainly, there are no “classes” in JavaScript to instantiate, only objects. And objects don’t get copied to other objects, they get linked together (more on that in Chapter 5).
Since observed class behaviors in other languages imply copies, let’s examine how JS developers fake the missing copy behavior of classes in JavaScript: mixins. We’ll look at two types of “mixin”: explicit and implicit.
Explicit Mixins
Let’s again revisit our Vehicle
and Car
example from before. Since JavaScript will not automatically copy behavior from Vehicle
to Car
, we can instead create a utility that manually copies. Such a utility is often called extend(..)
by many libraries/frameworks, but we will call it mixin(..)
here for illustrative purposes.
// vastly simplified `mixin(..)` example:
function mixin( sourceObj, targetObj ) {
for (var key in sourceObj) {
// only copy if not already present
if (!(key in targetObj)) {
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
var Vehicle = {
engines: 1,
ignition: function() {
console.log( "Turning on my engine." );
},
drive: function() {
this.ignition();
console.log( "Steering and moving forward!" );
}
};
var Car = mixin( Vehicle, {
wheels: 4,
drive: function() {
Vehicle.drive.call( this );
console.log( "Rolling on all " + this.wheels + " wheels!" );
}
} );
Note: Subtly but importantly, we’re not dealing with classes anymore, because there are no classes in JavaScript. Vehicle
and Car
are just objects that we make copies from and to, respectively.
Car
now has a copy of the properties and functions from Vehicle
. Technically, functions are not actually duplicated, but rather references to the functions are copied. So, Car
now has a property called ignition
, which is a copied reference to the ignition()
function, as well as a property called engines
with the copied value of 1
from Vehicle
.
Car
already had a drive
property (function), so that property reference was not overridden (see the if
statement in mixin(..)
above).
“Polymorphism” Revisited
Let’s examine this statement: Vehicle.drive.call( this )
. This is what I call “explicit pseudo-polymorphism”. Recall in our previous pseudo-code this line was inherited:drive()
, which we called “relative polymorphism”.
JavaScript does not have (prior to ES6; see Appendix A) a facility for relative polymorphism. So, because both Car
and Vehicle
had a function of the same name: drive()
, to distinguish a call to one or the other, we must make an absolute (not relative) reference. We explicitly specify the Vehicle
object by name, and call the drive()
function on it.
But if we said Vehicle.drive()
, the this
binding for that function call would be the Vehicle
object instead of the Car
object (see Chapter 2), which is not what we want. So, instead we use .call( this )
(Chapter 2) to ensure that drive()
is executed in the context of the Car
object.
Note: If the function name identifier for Car.drive()
hadn’t overlapped with (aka, “shadowed”; see Chapter 5) Vehicle.drive()
, we wouldn’t have been exercising “method polymorphism”. So, a reference to Vehicle.drive()
would have been copied over by the mixin(..)
call, and we could have accessed directly with this.drive()
. The chosen identifier overlap shadowing is why we have to use the more complex explicit pseudo-polymorphism approach.
In class-oriented languages, which have relative polymorphism, the linkage between Car
and Vehicle
is established once, at the top of the class definition, which makes for only one place to maintain such relationships.
But because of JavaScript’s peculiarities, explicit pseudo-polymorphism (because of shadowing!) creates brittle manual/explicit linkage in every single function where you need such a (pseudo-)polymorphic reference. This can significantly increase the maintenance cost. Moreover, while explicit pseudo-polymorphism can emulate the behavior of “multiple inheritance”, it only increases the complexity and brittleness.
The result of such approaches is usually more complex, harder-to-read, and harder-to-maintain code. Explicit pseudo-polymorphism should be avoided wherever possible, because the cost outweighs the benefit in most respects.
Mixing Copies
Recall the mixin(..)
utility from above:
// vastly simplified `mixin()` example:
function mixin( sourceObj, targetObj ) {
for (var key in sourceObj) {
// only copy if not already present
if (!(key in targetObj)) {
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
Now, let’s examine how mixin(..)
works. It iterates over the properties of sourceObj
(Vehicle
in our example) and if there’s no matching property of that name in targetObj
(Car
in our example), it makes a copy. Since we’re making the copy after the initial object exists, we are careful to not copy over a target property.
If we made the copies first, before specifying the Car
specific contents, we could omit this check against targetObj
, but that’s a little more clunky and less efficient, so it’s generally less preferred:
// alternate mixin, less "safe" to overwrites
function mixin( sourceObj, targetObj ) {
for (var key in sourceObj) {
targetObj[key] = sourceObj[key];
}
return targetObj;
}
var Vehicle = {
// ...
};
// first, create an empty object with
// Vehicle's stuff copied in
var Car = mixin( Vehicle, { } );
// now copy the intended contents into Car
mixin( {
wheels: 4,
drive: function() {
// ...
}
}, Car );
Either approach, we have explicitly copied the non-overlapping contents of Vehicle
into Car
. The name “mixin” comes from an alternate way of explaining the task: Car
has Vehicle
s contents mixed-in, just like you mix in chocolate chips into your favorite cookie dough.
As a result of the copy operation, Car
will operate somewhat separately from Vehicle
. If you add a property onto Car
, it will not affect Vehicle
, and vice versa.
Note: A few minor details have been skimmed over here. There are still some subtle ways the two objects can “affect” each other even after copying, such as if they both share a reference to a common object (such as an array).
Since the two objects also share references to their common functions, that means that even manual copying of functions (aka, mixins) from one object to another doesn’t actually emulate the real duplication from class to instance that occurs in class-oriented languages.
JavaScript functions can’t really be duplicated (in a standard, reliable way), so what you end up with instead is a duplicated reference to the same shared function object (functions are objects; see Chapter 3). If you modified one of the shared function objects (like ignition()
) by adding properties on top of it, for instance, both Vehicle
and Car
would be “affected” via the shared reference.
Explicit mixins are a fine mechanism in JavaScript. But they appear more powerful than they really are. Not much benefit is actually derived from copying a property from one object to another, as opposed to just defining the properties twice, once on each object. And that’s especially true given the function-object reference nuance we just mentioned.
If you explicitly mix-in two or more objects into your target object, you can partially emulate the behavior of “multiple inheritance”, but there’s no direct way to handle collisions if the same method or property is being copied from more than one source. Some developers/libraries have come up with “late binding” techniques and other exotic work-arounds, but fundamentally these “tricks” are usually more effort (and lesser performance!) than the pay-off.
Take care only to use explicit mixins where it actually helps make more readable code, and avoid the pattern if you find it making code that’s harder to trace, or if you find it creates unnecessary or unwieldy dependencies between objects.
If it starts to get harder to properly use mixins than before you used them, you should probably stop using mixins. In fact, if you have to use a complex library/utility to work out all these details, it might be a sign that you’re going about it the harder way, perhaps unnecessarily. In Chapter 6, we’ll try to distill a simpler way that accomplishes the desired outcomes without all the fuss.
Parasitic Inheritance
A variation on this explicit mixin pattern, which is both in some ways explicit and in other ways implicit, is called “parasitic inheritance”, popularized mainly by Douglas Crockford.
Here’s how it can work:
// "Traditional JS Class" `Vehicle`
function Vehicle() {
this.engines = 1;
}
Vehicle.prototype.ignition = function() {
console.log( "Turning on my engine." );
};
Vehicle.prototype.drive = function() {
this.ignition();
console.log( "Steering and moving forward!" );
};
// "Parasitic Class" `Car`
function Car() {
// first, `car` is a `Vehicle`
var car = new Vehicle();
// now, let's modify our `car` to specialize it
car.wheels = 4;
// save a privileged reference to `Vehicle::drive()`
var vehDrive = car.drive;
// override `Vehicle::drive()`
car.drive = function() {
vehDrive.call( this );
console.log( "Rolling on all " + this.wheels + " wheels!" );
};
return car;
}
var myCar = new Car();
myCar.drive();
// Turning on my engine.
// Steering and moving forward!
// Rolling on all 4 wheels!
As you can see, we initially make a copy of the definition from the Vehicle
“parent class” (object), then mixin our “child class” (object) definition (preserving privileged parent-class references as needed), and pass off this composed object car
as our child instance.
Note: when we call new Car()
, a new object is created and referenced by Car
s this
reference (see Chapter 2). But since we don’t use that object, and instead return our own car
object, the initially created object is just discarded. So, Car()
could be called without the new
keyword, and the functionality above would be identical, but without the wasted object creation/garbage-collection.
Implicit Mixins
Implicit mixins are closely related to explicit pseudo-polymorphism as explained previously. As such, they come with the same caveats and warnings.
Consider this code:
var Something = {
cool: function() {
this.greeting = "Hello World";
this.count = this.count ? this.count + 1 : 1;
}
};
Something.cool();
Something.greeting; // "Hello World"
Something.count; // 1
var Another = {
cool: function() {
// implicit mixin of `Something` to `Another`
Something.cool.call( this );
}
};
Another.cool();
Another.greeting; // "Hello World"
Another.count; // 1 (not shared state with `Something`)
With Something.cool.call( this )
, which can happen either in a “constructor” call (most common) or in a method call (shown here), we essentially “borrow” the function Something.cool()
and call it in the context of Another
(via its this
binding; see Chapter 2) instead of Something
. The end result is that the assignments that Something.cool()
makes are applied against the Another
object rather than the Something
object.
So, it is said that we “mixed in” Something
s behavior with (or into) Another
.
While this sort of technique seems to take useful advantage of this
rebinding functionality, it is the brittle Something.cool.call( this )
call, which cannot be made into a relative (and thus more flexible) reference, that you should heed with caution. Generally, avoid such constructs where possible to keep cleaner and more maintainable code.