Warning: This is a long post.
Let's keep it simple. I want to avoid having to prefix the new operator every time I call a constructor in JavaScript. This is because I tend to forget it, and my code screws up badly.
The simple way around this is this...
function Make(x) {
if ( !(this instanceof arguments.callee) )
return new arguments.callee(x);
// do your stuff...
}
But, I need this to accept variable no. of arguments, like this...
m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');
The first immediate solution seems to be the 'apply' method like this...
function Make() {
if ( !(this instanceof arguments.callee) )
return new arguments.callee.apply(null, arguments);
// do your stuff
}
This is WRONG however -- the new object is passed to the apply method and NOT to our constructor arguments.callee.
Now, I've come up with three solutions. My simple question is: which one seems best. Or, if you have a better method, tell it.
First – use eval() to dynamically create JavaScript code that calls the constructor.
function Make(/* ... */) {
if ( !(this instanceof arguments.callee) ) {
// collect all the arguments
var arr = [];
for ( var i = 0; arguments[i]; i++ )
arr.push( 'arguments[' + i + ']' );
// create code
var code = 'new arguments.callee(' + arr.join(',') + ');';
// call it
return eval( code );
}
// do your stuff with variable arguments...
}
Second – Every object has __proto__ property which is a 'secret' link to its prototype object. Fortunately this property is writable.
function Make(/* ... */) {
var obj = {};
// do your stuff on 'obj' just like you'd do on 'this'
// use the variable arguments here
// now do the __proto__ magic
// by 'mutating' obj to make it a different object
obj.__proto__ = arguments.callee.prototype;
// must return obj
return obj;
}
Third – This is something similar to second solution.
function Make(/* ... */) {
// we'll set '_construct' outside
var obj = new arguments.callee._construct();
// now do your stuff on 'obj' just like you'd do on 'this'
// use the variable arguments here
// you have to return obj
return obj;
}
// now first set the _construct property to an empty function
Make._construct = function() {};
// and then mutate the prototype of _construct
Make._construct.prototype = Make.prototype;
eval solution seems clumsy and comes with all the problems of "evil eval".
__proto__ solution is non-standard and the "Great Browser of mIsERY" doesn't honor it.
The third solution seems overly complicated.
But with all the above three solutions, we can do something like this, that we can't otherwise...
m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');
m1 instanceof Make; // true
m2 instanceof Make; // true
m3 instanceof Make; // true
Make.prototype.fire = function() {
// ...
};
m1.fire();
m2.fire();
m3.fire();
So effectively the above solutions give us "true" constructors that accept variable no. of arguments and don't require new. What's your take on this.
-- UPDATE --
Some have said "just throw an error". My response is: we are doing a heavy app with 10+ constructors and I think it'd be far more wieldy if every constructor could "smartly" handle that mistake without throwing error messages on the console.