Mixins in JavaScript

In object-oriented programming, a mixin is a class that contains a combination of methods from other classes.

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. However, we can fake the missing copy behavior of classes in JavaScript using mixins, either explicitly or implicitly. Let’s see a mixin example:

// 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!"
);
}
} );

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.

Let’s examine this statement: Vehicle.drive.call( this ). 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 itBut if we said Vehicle.drive(), the this binding for that function call would be the Vehicle object instead of the Car object, which is not what we want. So, instead we use .call( this ) to ensure that drive() is executed in the context of the Car object. If the function name identifier for Car.drive() hadn’t overlapped with 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().

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.

Consider this code for an implicit mixin:

 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) 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’s a 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 wherever possible to keep cleaner and more maintainable code.

Ref: Book: this & Object Prototypes

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s