JavaScript Mixins

Mixins (a.k.a. modules) are a convenient and useful way to package up pieces of behavior.

  /**
  * Bindable module
  * @name Bindable
  * @constructor
  */
  function Bindable() {
 
    var eventCallbacks = {};
 
    return {
      /**
      * The bind method adds a function as an event listener.
      *
      * @name bind
      * @methodOf Bindable#
      *
      * @param {String} event The event to listen to.
      * @param {Function} callback The function to be called when the specified event
      * is triggered.
      */
      bind: function(event, callback) {
        eventCallbacks[event] = eventCallbacks[event] || [];
 
        eventCallbacks[event].push(callback);
      },
      /**
      * The trigger method calls all listeners attached to the specified event.
      *
      * @name trigger
      * @methodOf Bindable#
      *
      * @param {String} event The event to trigger.
      */
      trigger: function(event) {
        var callbacks = eventCallbacks[event];
 
        if(callbacks && callbacks.length) {
          var self = this;
          callbacks.each(function(callback) {
            callback(self);
          });
        }
      },
    };
  }

On the face of it this Module only constructs a simple object with two methods bind and trigger. A statically typed language may make use of delegation to forward method calls to a Bindable object created for each class instance, but in dynamic languages we can literally mix this object into the object that wants to make use of it’s capabilities.

function Guy(I) {
  I = I || {};
 
  $.reverseMerge(I, {
    name: "Sancho"
  });
 
  var self = {
    sayHi: function() {
      alert("Hi, my name is " + I.name);
      // Make use of the method provided by the Bindable class
      self.trigger("spoke");
    }
  };
 
  // Mixing it in, just smash the methods of the newly created Bindable onto this object
  $.extend(self, Bindable());
 
  return self;
}
 
var guy = Guy();
 
guy.bind("spoke", function() {
  console.log("This guy spoke");
});
 
guy.sayHi(); // Alerts and logs to console

Some modules may require a method to be provided by the host. A classic example is an Enumerable module which can provide many additional iterators if given a standard each iterator.

function Enumerable() {
  return {
    partition: function(iterator, context) {
      var trueCollection = [];
      var falseCollection = [];
 
      this.each(function(element) {
        if(iterator.call(context, element)) {
          trueCollection.push(element);
        } else {
          falseCollection.push(element);
        }
      });
 
      return [trueCollection, falseCollection];
    },
 
    select: function(iterator, context) {
      return this.partition(iterator, context)[0];
    },
 
    reject: function(iterator, context) {
      return this.partition(iterator, context)[1];
    },
 
    shuffle: function() {
      var shuffledArray = [];
 
      this.each(function(element) {
        shuffledArray.splice(rand(shuffledArray.length + 1), 0, element);
      });
 
      return shuffledArray;
    }
  }
}

This Enumerable mixin provides partition, shuffle, select and reject methods. It relies on the host providing the each iterator, and makes use of it to provide partition and shuffle. The select and reject methods in turn make use of the partition method.

// Mixing enumerable into Array.prototype
 
//Alias forEach as each to provide the method by a name Enumerable knows
Array.prototype.each = Array.prototype.forEach;
 
// Mix it in
$.extend(Array.prototype, Enumerable());
 
[1, 2, 3, 4, 5, 6, 7, 8].select(function(n) {return n % 2 == 0}); // => [2, 4, 6, 8]

This covers the classic uses of mixins, though there are some extra cool things that you can do when using instance variables. For now I’ll leave that as an exercise for the reader but later I’ll spell it all out in a follow up post and in my upcoming book Daniel X. Moore’s {SUPER: SYSTEM}.

Leave a Reply

Your email address will not be published. Required fields are marked *