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: 556

Filed under:
|

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

Related posts about best-practices

Related posts about JavaScript