Avoiding new operator in JavaScript -- the better way
Posted
by
greengit
on Programmers
See other posts from Programmers
or by greengit
Published on 2011-11-09T16:55:14Z
Indexed on
2011/11/30
2:05 UTC
Read the original article
Hit count: 562
best-practices
|JavaScript
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.
© Programmers or respective owner