views:

231

answers:

2

I have written the following function that permits creation of singleton classes from given classes:

function SingletonFrom(Constructor) {
    return function() {
        var self = arguments.callee;
        if (self._instance === undefined) {
            switch (arguments.length) { // this is ugly
                case  0: self._instance = new Constructor(); break;
                case  1: self._instance = new Constructor(arguments[0]); break;
                case  2: self._instance = new Constructor(arguments[0], arguments[1]); break;
                // [...]
                case 10: // ...
                default: throw new Error('Error in Singleton: Constructor may take at most 10 arguments'); break;
            }
        }
        return self._instance;
    }
}

Example:

var Array_Singleton = new SingletonFrom(Array);
var x = new Array_Singleton(1,2,3);   // [1,2,3]
var y = new Array_Singleton(0,0,0,0); // [1,2,3]
alert(x === y); // true

It works great, however I'm not quite happy with the switch statement. The problem is passing a variable number of arguments to a constructor called with the "new" keyword is not possible. So, my Constructor function must not take more than 10 arguments. For example, this will fail:

new Array_Singleton(1,2,3,4,5,6,7,8,9,10,11);

Any way to work around this?

A: 

Off the top of my head, I can only think of one and I probably wouldn't recommend it without an extremely hefty amount of testing.

That being said; instead of passing in your parameters seperately, pass them as an array. You can then iterate through the array and build a string with your call to new. Once you have your string, you can call eval() to run your generated command.

That will give you a way to handle any dynamic number of parameters.

Justin Niessner
+1  A: 

Javascript has this annoying limitation of not being able to call constructor with an array as a list of arguments. What's usually accomplished with Function.prototype.apply when calling function as a function (e.g. foo(...)), can't be easily applied to a function when it's called as constructor (e.g. new foo(...)).

I'm guessing this is exactly why you resort to switch there.

function foo(a,b) {
  return a+b;
}
foo.apply(null, [1,2]); // 3

But not so easy with constructor:

function Person(fname, lname) {
  this.fname = fname;
  this.lname = lname;
}
Person.prototype.speak = function() {
  return 'Hi. My name is: ' + this.fname + ' ' + this.lname;
}

new Person.apply(null, ['John', 'Appleseed']); // doesn't work!

To work around that, you can create a simple helper which would practically simulate what new does, only this time with apply. The algorithm is straight-forward:

  1. Create an empty object with proper prototype chain, but don't call constructor on it.
  2. Use apply to call constructor in context of this newly created object, passing it list of arguments with apply.

It would look something like this:

function newApply(ctr, array) {
  function F(){}
  F.prototype = ctr.prototype;
  var obj = new F();
  ctr.apply(obj, array);
  return obj;
}
newApply(Person, ['John', 'Appleseed']); // returns new object

Alternatively, you could avoid F object creation at run time to save on performance and memory consumption:

var newApply = (function(){
  function F(){}
  return function(ctr, array) {
    F.prototype = ctr.prototype;
    var obj = new F();
    ctr.apply(obj, array);
    return obj;
  }
})();
kangax
Did you get that from here? http://stackoverflow.com/questions/813383/how-can-i-construct-an-object-using-an-array-of-values-for-parameters-rather-tha Regardless, the same caveats that were mentioned over there apply here (no pun intended ;): this does not work with *any* of the built-ins, in any browser. For example `newApply(Date, [2009,5,5])` fails, `newApply(Array, [5,5,5])` fails, etc etc.
Crescent Fresh
IOWs, you should mention this caveat to make an otherwise good answer even better.
Crescent Fresh
@crescentfresh Whoops, I should have searched for `newApply` instead of repeating what was already said :) I've been using it for a couple of years now but never with native constructors. It's a good point you're bringing. Now that I look at it, it makes sense that `newApply` wouldn't work with, say, `Date`, since `Date` can not be successfully "subclassed" (derived object would have proper [[Prototype]] but its [[Class]] would be "Object", not an "Date").
kangax