Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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.

share|improve this question
2  
or just throw a error when it's bad examine the stacktrace and you can fix the code –  ratchet freak Nov 9 '11 at 17:05
2  
I think this question would be better asked on Stack Overflow or Code Review. It seems to be pretty code-centric rather than a conceptual question. –  Anna Lear Nov 9 '11 at 17:23
1  
@greengit rather then throwing an error use a jslint. It will warn you if you did Make() without new because Make is capitalized and thus it assumes it is a constructor –  Raynos Nov 9 '11 at 17:35
1  
So wait - are you looking for a better way to accomplish this, or are you just looking for someone to give you code so you can have variable-argument object creation without new? Because if it's the latter, you're probably asking on the wrong site. If it's the former, you might want to not dismiss suggestions regarding using new and detecting errors so quickly... If your app is truly "heavy", the last thing you want is some overwrought construction mechanism to slow it down. new, for all the flack it gets, is pretty speedy. –  Shog9 Nov 9 '11 at 17:45
3  
Ironically, trying to 'smartly' handle programmer mistakes is itself responsible for many of the 'bad parts' of JavaScript. –  Daniel Pratt Jun 14 '12 at 22:41
show 1 more comment

5 Answers

up vote 11 down vote accepted

Firstly arguments.callee is deprecated in ES5 strict so we don't use it. The real solution is rather simple.

You don't use new at all.

var Make = function () {
  if (Object.getPrototypeOf(this) !== Make.prototype) {
    var o = Object.create(Make.prototype);
    o.constructor.apply(o, arguments);
    return o;
  }
  ...
}

That's a right pain in the ass right?

Try enhance

var Make = enhance(function () {
  ...
});

var enhance = function (constr) {
  return function () {
    if (Object.getPrototypeOf(this) !== constr.prototype) {
      var o = Object.create(constr.prototype);
      constr.apply(o, arguments);
      return o;
    }
    return constr.apply(this, arguments);
  }
}

Now of course this requires ES5, but everyone uses the ES5-shim right?

You may also be interested in alternative js OO patterns

As an aside you can replace option two with

var Make = function () {
  var that = Object.create(Make.prototype);
  // use that 

  return that;
}

In case you want your own ES5 Object.create shim then it's really easy

Object.create = function (proto) {
  var dummy = function () {};
  dummy.prototype = proto;
  return new dummy;
};
share|improve this answer
    
Yes true -- but all the magic here is because of Object.create. What about pre ES5? ES5-Shim lists Object.create as DUBIOUS. –  good_computer Nov 9 '11 at 17:14
    
@greengit if you read it again, ES5 shim states the second argument to Object.create is DUBIOUS. the first one is fine. –  Raynos Nov 9 '11 at 17:17
1  
Yes I did read that. And I think (IF) they're using some kind of __proto__ thing there, then we're still on the same point. Because pre ES5 there's NO easier way to mutate the prototype. But anyway, your solution seems most elegant and forward looking. +1 for that. (my voting limit is reached) –  good_computer Nov 9 '11 at 17:26
1  
Thanks @psr. And @Raynos your Object.create shim is pretty much my third solution but less complicated and better looking than mine of course. –  good_computer Nov 9 '11 at 17:47
add comment

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 obvious answer would be don't forget the new keyword.

You're changing the structure and meaning of the language.

Which, in my opinion, and for the sake of the future maintainers of your code, is a horrible idea.

share|improve this answer
5  
+1 for the plea to maintainability. –  Mike Partridge Nov 23 '11 at 14:02
4  
+1 It seems odd to wrangle a language around one's bad coding habits. Surely some syntax highlighting/check-in policy can enforce avoidance of bug-prone patterns/probable typos. –  Stoive Nov 30 '11 at 0:22
add comment

Easiest solution is to just remember new and throw an error to make it obvious you forgot.

if (Object.getPrototypeOf(this) !== Make.prototype) {
    throw new Error('Remember to call "new"!');
}

Whatever you do, don't use eval. I would shy away from using non-standard properties like __proto__ specifically because they are non-standard and their functionality may change.

share|improve this answer
    
Defiantly avoid .__proto__ it is the devil –  Raynos Nov 9 '11 at 17:22
add comment

How About This?

/* thing maker! it makes things! */
function thing(){
    if (!(this instanceof thing)){
        /* call 'new' for the lazy dev who didn't */
        return new thing(arguments, "lazy");
    };

    /* figure out how to use the arguments object, based on the above 'lazy' flag */
    var args = (arguments.length > 0 && arguments[arguments.length - 1] === "lazy") ? arguments[0] : arguments;

    /* set properties as needed */
    this.prop1 = (args.length > 0) ? args[0] : "nope";
    this.prop2 = (args.length > 1) ? args[1] : "nope";
};

/* create 3 new things (mixed 'new' and 'lazy') */
var myThing1 = thing("woo", "hoo");
var myThing2 = new thing("foo", "bar");
var myThing3 = thing();

/* test your new things */
console.log(myThing1.prop1); /* outputs 'woo' */
console.log(myThing1.prop2); /* outputs 'hoo' */

console.log(myThing2.prop1); /* outputs 'foo' */
console.log(myThing2.prop2); /* outputs 'bar' */

console.log(myThing3.prop1); /* outputs 'nope' */
console.log(myThing3.prop2); /* outputs 'nope' */

EDIT: I forgot to add:

"If your app is truly 'heavy', the last thing you want is some overwrought construction mechanism to slow it down"

I absolutely agree - when creating 'thing' above without the 'new' keyword, it is slower/heavier than with it. Errors are your friend, because they tell you what's wrong. Moreover, they tell your fellow developers what they're doing wrong.

share|improve this answer
    
Inventing values for missing constructor arguments is error prone. If they're missing, leave the properties undefined. If they're essential, throw an error if they're missing. What if prop1 was supposed to be bool? Testing "nope" for truthiness is going to be a source of errors. –  JBRWilkinson Jun 14 '12 at 23:56
add comment

I actually wrote a post about this. http://js-bits.blogspot.com/2010/08/constructors-without-using-new.html

function Ctor() {
    if (!(this instanceof Ctor) {
        return new Ctor(); 
    }
    // regular construction code
}

And you can even generalize it so you don't have to add that to the top of every constructor. You can see that by visiting my post

Disclaimer I don't use this in my code, I only posted it for the didactic value. I found that forgetting a new is an easy bug to spot. Like others, I don't think we actually need this for most code. Unless you're writing a library for creating JS inheritance, than you could use from one single place and you would already be using a different approach than the straight forward inheritance.

share|improve this answer
    
Potentially hidden bug: if I have var x = new Ctor(); and then later have x as this and do var y = Ctor();, this wouldn't behave as expected. –  luiscubal Aug 19 '12 at 1:50
    
@luiscubal Not sure what you are saying "later have x as this", can you maybe post a jsfiddle to show the potential problem? –  Juan Mendes Mar 29 '13 at 18:34
    
Your code is a bit more robust than I initially thought, but I did manage to come up with a (somewhat convoluted but still valid) example: jsfiddle.net/JHNcR/1 –  luiscubal Mar 29 '13 at 19:16
    
@luiscubal I see your point but it really is a convoluted one. You are assuming that it's OK to call Ctor.call(ctorInstance, 'value'). I don't see a valid scenario for what you're doing. To construct an object, you either use var x = new Ctor(val) or var y=Ctor(val). Even if there was a valid scenario, my claim is that you can have constructors without using new Ctor, not that you can have constructors that work using Ctor.call See jsfiddle.net/JHNcR/2 –  Juan Mendes Mar 29 '13 at 20:56
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.