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