Another Approach To Mixins (First Draft)

Synopsis

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.

The Problem

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.

A Possible Solution

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;
  };

Explanation

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.

Code Walkthrough

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
name inner closure "notify"

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);

Refinements

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;
  }());

Advantages

Simplicity

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.

Flexibility

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);

Disadvantages

Memory Usage

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.

Running the Prototype Functions Wreaks Havoc

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."

And Th-th-th-that's All, Folks

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.


(Created May 3, 2012. Last updated May 3, 2012)

Version History