0

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?

user123444555621
  • 148,182
  • 27
  • 114
  • 126
  • x == y resulting in true?? Why would you want that in your example above?? Looks extremely dangerous to me. – Juri Sep 23 '09 at 13:25
  • In certain situations, this might be useful. First come, first serve... In my current project, my Obj_Singleton constructors are all called with the very same arguments anyway, which makes things easier. Naturally, you could modify the method to report an error if an instance exists. – user123444555621 Sep 23 '09 at 14:40

2 Answers2

1

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
  • 38,898
  • 13
  • 99
  • 135
  • 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 Sep 23 '09 at 14:30
  • IOWs, you should mention this caveat to make an otherwise good answer even better. – Crescent Fresh Sep 23 '09 at 14:32
  • @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 Sep 25 '09 at 02:26
0

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
  • 242,243
  • 40
  • 408
  • 536