16

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.

6
  • 2
    or just throw a error when it's bad examine the stacktrace and you can fix the code Nov 9, 2011 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.
    – Adam Lear
    Nov 9, 2011 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, 2011 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, 2011 at 17:45
  • 5
    Ironically, trying to 'smartly' handle programmer mistakes is itself responsible for many of the 'bad parts' of JavaScript. Jun 14, 2012 at 22:41

5 Answers 5

19

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;
};
4
  • Yes true -- but all the magic here is because of Object.create. What about pre ES5? ES5-Shim lists Object.create as DUBIOUS.
    – treecoder
    Nov 9, 2011 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, 2011 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)
    – treecoder
    Nov 9, 2011 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.
    – treecoder
    Nov 9, 2011 at 17:47
37

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.

4
  • 9
    +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, 2011 at 0:22
  • If I found a library where all constructors didn't care if you used new or not, I'd find that more maintainable.
    – Jack
    May 22, 2015 at 18:58
  • @Jack, but it will introduce much harder and subtler to find bugs. Just look around at all the bugs caused by javascript "not caring" if you include the ; to end statements. (Automatic Semicolon Insertion)
    – CaffGeek
    May 23, 2015 at 16:13
  • @CaffGeek "Javascript not caring about semicolons" isn't accurate - there are cases where using a semicolon and not using it are subtly different, and there are cases where they aren't. That's the problem. The solution presented in the accepted answer is precisely the opposite - with it, in all cases using new or not are semantically identical. There are no subtle cases where this invariant is broken. That's why it's good and why you'd want to use it.
    – Jack
    May 23, 2015 at 20:33
14

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.

1
  • Defiantly avoid .__proto__ it is the devil
    – Raynos
    Nov 9, 2011 at 17:22
3

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, in which case you could use from one single place and you would already be using a different approach than the straight forward inheritance.

4
  • 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, 2012 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? Mar 29, 2013 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, 2013 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 Mar 29, 2013 at 20:56
0

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.

1
  • 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. Jun 14, 2012 at 23:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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