Zebra Kick Blog

Justin

Generic Singleton Factory in JavaScript

Sunday, August 16th, 2009

I recently contributed this little tidbit to stackoverflow’s Hidden Features of JavaScript and thought it would be best to elaborate it on it fully. In a recent client project, I needed a generic method to produce singleton instances of interface widgets.  This is the method that I came up with and it works pretty well. Before we begin, these are not singleton classes in the normal sense, but a generic singleton factory.

Lets get started, piece by piece.   First off, we need a means of storing instances internally.  To do that, we need a static variable within our function.

var getInstance = function(objectName) {
  if ( !getInstance.instances ) {
    getInstance.instances = {};
  }
}

It doesn’t do much, but there you have it.  Now that we have somewhere to store our instances, lets finish out the function by instantiating new objects (only once of course) and returning those singleton instances.

var getInstance = function(objectName) {
  if ( !getInstance.instances ) {
    getInstance.instances = {};
  }

  if ( !getInstance.instances[objectName] ) {
    getInstance.instances[objectName] = new window[objectName]();
  }

  return getInstance.instances[objectName];
}

And that’s it.  Notice the new window[objectName]();.  In JavaScript, all objects are contained within/are relative to the window object.

Consider the following two example classes:

// Normal, globally accessible object
var object = function() {
 this.property = "zebras!"
};

// Static object acting as a namespace
var com = {
  project: {
    widgetAbc: function() {
      this.xyz = 1010101;
    }
  }
};

Basic usage of our new generic singleton factory is as follows:

var instance = getInstance("object");

So far, getInstance works in most instances, but if you’re in an environment where you’re using static, nested objects to create your own brand of namespacing (as in getInstance("com.project.widgetAbc")), then this method falls a little short as is; however, there is solution.

var getInstance = function(objectName) {
  // Static instances container
  if ( !getInstance.instances ) {
    getInstance.instances = {};
  }

  // If an instance hasn't been created yet...
  if ( !getInstance.instances[objectName] ) {
    // All instances are relative to the window object
    var object = window;
    // Handle static object nesting/chaining by splitting on the
    // object separator "."
    var parts  = objectName.split(".");

    // Traverse through the nested static objects until we reach
    // the last one in the chain.
    for ( var i in parts ) {
      object = object[ parts[i] ];
    }

    // Create and store the object's instance
    getInstance.instances[objectName] = new object;
  }

  // Return the stored object's instance
  return getInstance.instances[objectName];
}

I think the comments spell out the changes fairly well, but lets give it a once-over just to be sure.  The major change is that objectName is split on the member operator (.), yielding any array like ["com", "project", "widgetAbc"].  We then iterate through that array, nesting each member within itself starting from the window object until we reach the end of the chain.  This gives us an instantiable reference to the requested class.

Lastly, you’ll notice that on the line, getInstance.instances[objectName] = new object;, I omitted the ().  It works with or without the parenthesis, but I prefer to omit them to distinguish the fact that we are not instantiating an object called “object”, but instead, are instantiating the object referenced by the variable “object.”

Now that we have all that settled, we can use our factory as follows:

var someObject = getInstance("object");
var someWidget = getInstance("com.project.widgetAbc");

I hope that shed some light on the subject.  Feel free to ask a question or two, and don’t hesitate to tell me if I made any mistakes that need correcting.

3 Responses to “Generic Singleton Factory in JavaScript”

Leave a Reply


Comments links could be nofollow free.