Generic Singleton Factory in JavaScript
Sunday, August 16th, 2009I 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.
A flyweight might be a better approach for the task you described, instead of a singleton that relies on the objects constructor to initialize all of its properties.
Actually, since in this example the singleton constructor takes no arguments, it would rely completely on getters/setters to initialize properties. However, it would be trivially simple to pass an arbitrary amount of arguments from getInstance to the specified object constructor, but it’s left as an exercise for the reader.
Kick-ass blogpost, great looking blog, added it to my favorites!!