Early Mixins, Late Mixins
In JavaScript, the language supplies us with several code reuse patterns. Prototype chaining gives us the primary single inheritance mechanism. Mixins are also an important pattern when we want one object to “inherit” functionality from multiple objects. There are several ways to implement the mixin process. This article looks at two contrasting implementations: early mixins and late mixins.
Late Binding
Let’s start with a quick review of what late binding gives us.
var adam = {
greet: function() {
return "hello";
}
};
adam.greet(); // "hello"
When we ask adam
to greet
, the method to be executed is looked up at the time of the call. This is late binding and means if we redefine the greet method, subsequent calls will use the new definition.
adam.greet = function(name) {
return "hello" + (name ? (", " + name) : "");
};
adam.greet("world"); // "hello, world";
This ability to redefine a method is valuable because it makes it possible for plugins to modify the behaviour of an existing code library.
Late Binding and Prototype Chaining
How does late binding work with prototype chaining? Let’s start with a fresh example. First we’ll make a new object adam
who introduces himself.
var adam = {
_name: "Adam";
greet: function() {
return "hello. I am " + this._name;
}
};
Someone named Adam seems like a suitable prototype for all existing people. We can make a constructor function to produce more people.
function Person(name) {
this._name = name;
}
Person.prototype = adam;
var eve = new Person("Eve");
eve.greet(); // "hello. I am Eve"
Thanks to the combination of late binding and the prototype chain, a change to adam
will result in a change to eve
instantly. The following could be some plugin for the library that provides the adam
object. This redefinition could happen at runtime due to user interaction.
adam.greet = function(name) {
return "hello" + (name ? (", " + name) : '') + ". I am " + this._name;
};
eve.greet("world"); // "hello, world. I am Eve"
Early Mixins
Another way to create a reusable bunch of code as a library is to provide one object who’s properties can be mixed into another object. This is usually done with an early mixin implementation.
First, a generic function that can mix properties from one object into another object.
function earlyMixin(sink, source) {
for (var property in source) {
sink[property] = source[property];
}
}
Now a object containing methods that would be useful on another object. Here is a library called Speeches.JS.
var speeches = {
greet: function() {
return "hello. I am " + this._name;
},
farewell: function() {
return "goodbye. I am " + this._name;
}
};
Since this Speeches.JS library is already written and well unit tested, we’d like to reuse that code in our code.
function Person(name) {
this._name = name;
}
earlyMixin(Person.prototype, speeches);
var adam = new Person('Adam');
adam.greet(); // "hello. I am Adam"
Now suppose we have mixed this speeches
object into several other objects (like the Person.prototype
object.) Also suppose we want to modify the speeches
object at some later time and have all objects who’ve had the speeches
object mixed into it be updated. This is what a plugin for Speeches.JS might want to do.
speeches.greet = function(name) {
return "hello" + (name ? (", " + name) : '') + ". I am " + this._name;
};
Unfortunately we cannot do this because the speeches
methods have been mixed into the Person.prototype
object early (i.e. at the time of mixin.) We still have
adam.greet("world"); // "hello. I am Adam"
Late Mixins
In order to make it possible to modify speeches
and have all objects use the modified methods, we need one level of indirection in the mixin process.
function lateMixin(sink, source) {
for (var property in source) {
(function(property) {
sink[property] = function() {
return source[property].apply(this, arguments);
}
}(property));
}
}
Instead of directly borrowing the methods of the source
, the sink
is given methods that call the methods of source
. This means that the method on source
are mixed in late because they are looked up when they are called (rather than when they are mixed in.)
Now we can go through the same example with a different result.
var speeches = {
greet: function() {
return "hello. I am " + this._name;
},
farewell: function() {
return "goodbye. I am " + this._name;
}
};
function Person(name) {
this._name = name;
}
lateMixin(Person.prototype, speeches);
var adam = new Person('Adam');
adam.greet(); // "hello. I am Adam"
speeches.greet = function(name) {
return "hello" + (name ? (", " + name) : '') + ". I am " + this._name;
};
adam.greet("world"); // "hello, world. I am Adam"
When adam.greet
is called it uses the most recent definition of speeches.greet
. Yay! Late mixins have given us the same dynamic power that late binding and the prototype chain give us. This makes a library like Speeches.JS more flexible and opens it up for various kinds of modification at run time.
Not Quite Everything
Late mixins give us some of the power of late binding and the prototype chain but not everything. If we add another method to the speeches
object it is not added to the other objects into which speeches
has been mixed. There are several ways to accomplish this type of functionality. Something like proxies might be our answer to get closer to real multiple inheritance.
Comments
Have something to write? Comment on this article.