We discuss a different style of Javascript mixins, one that creates a mixin function for a given API and a particular initialization function. It is both simpler to use and more flexible than many compteting techniques.
Mixins in Javascript have been discussed often. There are many implementations, although the basic patterns are much the same. But there has always been something unsatisfying about any of them, and I have been struggling to figure out a way to handle the conflicting pressures that mixins demonstrate.
On a recent bus ride I scratched out a possible solutions for this (pencil and paper -- yes, I'm that old!) and put it aside until I ran across Peter Michaux's new article. That made me want to see if my idea could work in practice. So I've tried it, and I think it's worth sharing.
Peter's article used the constructor Observable
, and I will stick with that
for my initial examples. Here is one version of that constructor:
var Observable = function() { this.observers = []; }; Observable.prototype.observe = function(observer) { this.observers.push(observer); }; Observable.prototype.notify = function(data) { for (var i = 0, ilen = this.observers.length; i < ilen; i++) { this.observers[i](data); } };
A mixin takes an API like Observable
and adds it to a different object, often
to the prototype for another constructor function. The API can be arbitrary, but
there is one caveat with most mixin techniques: if any initialization is
necessary for the API to work, it needs to be run on all instances of the
mixin target. Thus if we have a constructor like the following:
var Person = function(name) { this.setName(name); } // somehow mixin Observable to Person Person.prototype.setName = function(name) { var oldName = this.name; this.name = name; this.notify({oldName:oldName, newName:this.name}); }
We will have a problem with this:
var person = new Person("steve");
because it will be looking for this.observers
, which won't exist. One
technique that I've used before, and the one that Peter's article suggests, is
to add a call to the Observable
constructor inside the Person
constructor:
var Person = function(name) { Observable.call(this); this.setName(name); }
This works fine, but it causes us to have to know about the mixin when writing
the constructor. In this case, that's not an issue, since the Person
prototype
already uses one of the mixin methods (notify
) but it's an odd intrusion in
the general case.
I would really like a technique that handles this for us. The problem is that
when we mix our API into a constructor, there is no actual instance for us to
modify. There seems to be no opportunity to run the initialization code except
inside that constructor, unless we accept the ugly possibility of wrapping the
work of each of our mixin's methods inside if
-blocks that check to see if the
initialization has been run, running it if not. We like clean code; we really
don't want that. For a long time, I thought this was an intractable issue, but
I've recently been working with an approach that seems to solve it.
What if we could run some one-time initialization exactly in the case that our mixin API is accessed? That would take care of this thorny issue.
It turns out that this is not too hard to do. It's simply an extension of the notion of lazy-loading of functions, a topic well-covered in another excellent article by Peter Michaux. The only real difference is that in our case, instead of replacing a single function, we replace all the functions from the mixin API. Here's one implemenation of this concept:
var createMixinApi = function(api, initFn) { var name, result = {}; var createRealApi = function() { var method = Array.prototype.shift.call(arguments); initFn.call(this); for (name in api) {if (api.hasOwnProperty(name)){ this[name] = api[name]; }} return this[method].apply(this, arguments); }; for (name in api) {if (api.hasOwnProperty(name)) { result[name] = (function(name) { return function() { Array.prototype.unshift.call(arguments, name); return createRealApi.apply(this, arguments); } }(name)); }} return result; };
That's a bit of a mouthful. Let's break it down a bit. Here is the actual
function that will be mixed in to the target as the property whose name is
represented by the variable name
:
function() { Array.prototype.unshift.call(arguments, name); return createRealApi.apply(this, arguments); }
So after calling this:
extend(Person.prototype, createMixinApi(Observable.prototype, Observable))
Person.prototype would look something like this:
{ setName: function(newName) { /* existing implementation */ }, observe: function() { Array.prototype.unshift.call(arguments, name); return createRealApi.apply(this, arguments); }, notify: function() { Array.prototype.unshift.call(arguments, name); return createRealApi.apply(this, arguments); } }
This does not look too promising. Obviously observe
and notify
should not
have the same exact implementation. But in fact they don't. They are bound to
different closures. And the name
variable in the two functions is bound
differently in their closures (to 'observe' and 'notify'.) When we call the
functions, this makes all the difference.
Once one of these functions is invoked on the Person
instance, though, the
initialization is run, and all of them are replaced with the actual API of
Observable
. If you already understand how that happens, feel free to skip
the next section.
We'll examine in detail what happens when we do this:
var person = new Person("Steve");
The Person
constructor is invoked with the parameter "Steve". This calls the
prototype setName
function, passing "Steve". The third line of that function
calls the notify
method passing an object with the old and new names. This is
the first usage of the mixed in code, and it's where things get interesting.
We call the prototype notify
function, and when we enter this function, we have
these variables available:
Name | Defined in | Value |
---|---|---|
arguments | local function scope | ["Steve"] (an Arguments object, not an array) |
this | local function scope | Person object |
name | outer closure | "notify" |
api | inner closure | {notify: [a function], observe: [a function]} |
createRealApi | inner closure | [a function] |
initFn | inner closure | The Observer constructor function |
Note that the name
property from the inner closure is shadowed by the name
property from the outer closure. Although they have the same value right now,
that is only because the order they were iterated in the order they appeared in
the original Observable
definition. (Had observe
been defined after
notify
, then name
in the inner closure would now have the value "observe".)
Now we unshift the value name
onto the arguments
pseudo-array and call the
createRealApi
function. Inside that function, here are the variables:
Name | Defined in | Value |
---|---|---|
arguments | local function scope | ["notify", "Steve"] |
this | local function scope | Person object |
api | closure | {notify: [a function], observe: [a function]} |
createRealApi | closure | [a function] |
initFn | closure | The Observer constructor function |
name | closure | "notify" |
In this function, we pull the method name out of arguments
and call the
initialization function we supplied, which in this case is the Observable
constructor. That is called in the context of our new Person
object. (This is
the step I didn't want to do inside the Person
constructor.)
Then we add all the functions from the mixin to our instance. Note that these
functions will shadow the ones in the Person
prototype. At this point, we now
have the initialization of Observable
completed inside our Person
instance.
All that remains is to complete the call we were trying to make in the
beginning, which is accomplished by this line:
return this[method].apply(this, arguments);
The API This presents is still rather clunky. To actually use it, we need to
depend on an external extend
function, and we don't have a great location to
store our new mixin API.
The latter problem has a reasonable solution: Why not store it as a property of
the Observable
constructor itself? As to the former problem, Angus Croll makes
a good case for thinking of such mixin APIs as functions rather than as
static objects. Combining these, we would like an API that looks something like this:
Observable.mixinTo = createMixin(Observable.prototype, Observable); // ... and then later ... Observable.mixinTo(Person.prototype);
We can turn the above into such a reusable function-generator in a fairly straightforward manner:
var createMixin = (function() { var extend = function(destination, source) { for (var name in source) {if (source.hasOwnProperty(name)) { destination[name] = source[name]; }} return destination; } var createMixinApi = function(api, initFn) { var name, result = {}; var createRealApi = function() { var method = Array.prototype.shift.call(arguments); initFn.call(this); for (name in api) {if (api.hasOwnProperty(name)){ this[name] = api[name]; }} return this[method].apply(this, arguments); }; for (name in api) {if (api.hasOwnProperty(name)) { result[name] = (function(name) { return function() { Array.prototype.unshift.call(arguments, name); return createRealApi.apply(this, arguments); } }(name)); }} return result; }; return function(api, initFn) { var mixin = createMixinApi(api, initFn); return function(destination) { return extend(destination, mixin); }; }; }());
There is still one bit of clean-up that remains. For our current example, and probably for many others, the initialization function and the API to mixin are intimately related, as the former is a constructor function and the latter its prototype. There is definitely a bit of code smell to repeating the construtor name in this:
Observable.mixinTo = createMixin(Observable.prototype, Observable);
I probably would not want to eliminate the first use of Observable
because,
while it might sometimes be convenient to simply call a function passing a
constructor as a parameter, the code would be considerably less readable to
someone who didn't know what the Mixin function did. But the other two are
clearly repetitive. It would be cleaner, in this case, to simply call:
Observable.mixinTo = createMixin(Observable);
But there are other ways we would want to use this, ways in which the relationship between the API to mix in and initialization function is not so simple. I'll explain a few below. This means we wouldn't want that version of the function to be the only version available. To deal with this conflict, we could present any of these variations:
Observable.mixinTo = Mixin.create(Observable); var someOtherMixinFunction = Mixin.create(someAPI, someInitializater);
or
Observable.mixinTo = Mixin.create(Observable); var someOtherMixinFunction = Mixin.createApi(someApi, someInitializater);
or
Observable.mixinTo = createMixin(Observable); var someOtherMixinFunction = createMixin.forApi(someApi, someInitializater);
The first variation seems very popular these days in Javascript. The jQuery API, for instance, uses a great deal of method overloading. I'm not thrilled with this idea, myself; I don't think the smaller conceptual footprint is enough to outweigh the additional complexity in parameter handling. But its certainly a viable option.
The second variation is probably the most conventional. There is nothing wrong with it, and it would be easy to implement. But I find the third one more interesting, and perhaps slightly cleaner to use. So that is what I'll present as my completed API:
var createMixin = (function() { var extend = function(destination, source) { for (var name in source) {if (source.hasOwnProperty(name)) { destination[name] = source[name]; }} return destination; } var createMixinApi = function(api, initFn) { var name, result = {}; var createRealApi = function() { var method = Array.prototype.shift.call(arguments); initFn.call(this); for (name in api) {if (api.hasOwnProperty(name)){ this[name] = api[name]; }} return this[method].apply(this, arguments); }; for (name in api) {if (api.hasOwnProperty(name)) { result[name] = (function(name) { return function() { Array.prototype.unshift.call(arguments, name); return createRealApi.apply(this, arguments); } }(name)); }} return result; }; var create = function(api, initFn) { var mixin = createMixinApi(api, initFn); return function(destination) { return extend(destination, mixin); }; }; var createConstructorMixin = function(ctor) { return create(ctor.prototype, ctor); } createConstructorMixin.forApi = create; return createConstructorMixin; }());
The biggest advantage to this mixin strategy that I see is just how clean it is. You can create a mixin out of any API in a single line of code, and mix it into any other API with just one more. Neither the source nor the destination needs to know anything about the other.
But it is also flexible. Imagine an API that looks like this:
// creates Logger instances that can store and display log messages var Logger = function() { // initialize various listeners, do other setup }; Logger.prototype = { debug: function(msg) { /* ... */ }, // default: ignored info: function(msg) { /* ... */ }, // default: console.info warn: function(msg) { /* ... */ }, // default: console.warn error: function(msg) { /* ... */ }, // default: console.error addListener: function(msgTypes, newListener) { /* ... */ }, getMessages: function(type) { /* ... */ }, enable: function(type) { /* ... */ }, disable: function(type) { /* ... */ }, };
If we would like to create a mixin out of this so that other objects could use
its implementation of info
, warn
, and error
, but did not want the entire
Logger API mixed in, we could use the our mixin like this:
Logger.mixinTo = createMixin.forApi({ info: Logger.prototype.info, warn: Logger.prototype.warn, error: Logger.prototype.error }, Logger); // ... later ... Logger.mixinTo(MyObject.prototype);
If we also wanted the getMessages
functionality, but were concerned that this
name was too generic for our mixin, we could simply alias it as
getLogMessages
:
Logger.mixinTo = createMixin.forApi({ info: Logger.prototype.info, warn: Logger.prototype.warn, error: Logger.prototype.error, getLogMessages: Logger.prototype.getMessages }, Logger);
Or if our Logger constructor created a number of instance properties that were
unnecessary for our mixin case, we could instead use a custom function as the
initializer. This would require some knowledge of how the Logger
constructor
worked. Let's imagine, for instance, that if we don't need the addListener
,
enable
, or disable
properties of Logger, and that therefore the only
initialization required is to add the single property, messageStore
, to our
instance. Then we can supply a simple function like this:
Logger.mixinTo = createMixin.forApi({ info: Logger.prototype.info, warn: Logger.prototype.warn, error: Logger.prototype.error }, function() { this.messageStore = []; });
But perhaps we really want to expose a partial API delegating all calls to an instance created by the underlying constructor. We could do the following as a one-off:
Logger.mixinTo = createMixin.forApi({ info: function() {return this.logger.info.apply(this.logger, arguments);}, warn: function() {return this.logger.warn.apply(this.logger, arguments);}, error: function() {return this.logger.error.apply(this.logger, arguments);} }, function() { this.logger = new Logger();; });
Or if we thought this was common, we could add to our function something that would allow us to do:
Logger.mixinTo = createMixin.delegatedApi(["info", "warn", "error"], Logger); // ... and then later ... Logger.mixinTo(MyObject.prototype, "logger"); // second param is delegate name // ... var obj1 = new MyObject(/* params */); // next line resolves to obj1.logger.warn("Problem found!"); obj1.warn("Problem found!");
We'll leave details of how to write that as an exercise for the reader.
Also note that although we've mostly discussed mixing into the prototypes of constructor functions, in fact we can mix in to any object at all.
var obj1 = new MyObject(/* params */); Logger.mixinTo(obj1); var obj2 = { method1: function() {/* ... */}, method2: function() {/* ... */} }; Logger.mixinTo(obj2);
One issue that might be significant is that this style takes more memory than a pure constructor function approach. Each instance gets its own references to each mixed in API function. Or at least it gets them when the API is actually used; until then it simply inherits the wrapper functions attached to its constructor. Note though that it does not get its own copy of the function, only a copy of the reference to that function. So the memory overhead is not huge, but it's real, and there is no way to eliminate it.
Since in Javascript prototypes are simply plain objects, any of these wrapped functions could be called as methods of the prototype. No one should ever do something like this, of course, but if it did happen, it would cause significant problems. The initializer would run on the prototype, adding whatever properties it expected to add on an instance. The prototype wrapper functions would be replaced by the underlying API. All future instances created from that prototype would share a single state as would all instances that have not yet had their own wrapper functions replaced. This would be badTM.
But that is not a problem specific to this technique. It would happen for any lazily instantiated instance properties created by functions on a prototype:
Person.prototype.addFriend = function(friend) { if (!this.friends) { this.friends = []; } this.friends.push(friend); };
If addFriend
was called on the prototype, then all Person
instances that
have not yet had their own list of friends initialized (including all new Person
objects) would share a single list of friends. So, while this is a concern, it
seems to be more a case of "Doctor, it hurts when I do this. So don't do that."
This seems to be a useful technique, and it shows off some of the wonderful dynamic nature of Javascript.
Please feel free to look at the complete source or the unit tests, and let me know about any suggestions.