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.
Edit: Singletons are generally considered an anti-pattern. Use them at your own discretion.
Lets get started, piece by piece. First off, we need a means of storing instances internally. To do that, we create a closure and return a new function that will actually do the work:
1 2 3 4 5 6 |
var getInstance = (function() { var instances = {}; return function(objectName) { }; })(); |
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.
1 2 3 4 5 6 7 8 9 10 11 |
var getInstance = (function() { var instances = {}; return function(objectName) { if ( !instances[objectName] ) { instances[objectName] = new window[objectName](); } return instances[objectName]; }; })(); |
And that’s it. Notice the new window[objectName]();. In JavaScript, you can access objects in the global scope by name by referencing them as properties of the global window object.
Consider the following two example constructors:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Globally accessible constructor var Koolaid = function() { this.property = "Oh Ya!" }; // Constructor as part of an object literal acting as a namespace var com = { project: { widgetAbc: function() { this.xyz = 1010101; } } }; |
Basic usage of our fancy new generic singleton factory is as follows:
1 |
var instance = getInstance("Koolaid"); |
So far, getInstance works in simple cases, but if you’re in an environment where you’re using nested objects to facilitate namespacing (as in getInstance("com.project.widgetAbc")), then this method falls a little short. However, there is a simple solution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var getInstance = (function() { var instances = {}; return function(objectName) { var object, chain, i, len; if ( !instances[objectName] ) { // All instances are relative to the window object object = window; // Handle object nesting/chaining by splitting on the // object separator "." chain = objectName.split("."); // Traverse through the nested static objects until we reach // the last one in the chain. len = chain.length; for ( i = 0; i < len; ++i ) { object = object[ chain[i] ]; } // Create and store the object's instance instances[objectName] = new object; } return 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, linking each member to the previous, starting from the global object, window, until we reach the end of the chain. This gives us an instantiable reference to the requested object.
Lastly, you’ll notice that on the line, 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:
1 2 |
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.