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.
5 Answers 5
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;
};
-
Yes true -- but all the magic here is because of
Object.create
. What about pre ES5? ES5-Shim listsObject.create
as DUBIOUS.treecoder– treecoder11/09/2011 17:14:04Commented 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– Raynos11/09/2011 17:17:21Commented Nov 9, 2011 at 17:17
-
1Yes 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– treecoder11/09/2011 17:26:07Commented Nov 9, 2011 at 17:26 -
1Thanks @psr. And @Raynos your
Object.create
shim is pretty much my third solution but less complicated and better looking than mine of course.treecoder– treecoder11/09/2011 17:47:13Commented Nov 9, 2011 at 17:47
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.
-
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– Stoive11/30/2011 00:22:50Commented 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– Jack05/22/2015 18:58:17Commented 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– CaffGeek05/23/2015 16:13:38Commented 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– Jack05/23/2015 20:33:54Commented May 23, 2015 at 20:33
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.
-
Defiantly avoid
.__proto__
it is the devilRaynos– Raynos11/09/2011 17:22:50Commented Nov 9, 2011 at 17:22
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.
-
Potentially hidden bug: if I have
var x = new Ctor();
and then later have x asthis
and dovar y = Ctor();
, this wouldn't behave as expected.luiscubal– luiscubal08/19/2012 01:50:30Commented 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?Ruan Mendes– Ruan Mendes03/29/2013 18:34:44Commented 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/1luiscubal– luiscubal03/29/2013 19:16:25Commented 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 usevar x = new Ctor(val)
orvar y=Ctor(val)
. Even if there was a valid scenario, my claim is that you can have constructors without usingnew Ctor
, not that you can have constructors that work usingCtor.call
See jsfiddle.net/JHNcR/2Ruan Mendes– Ruan Mendes03/29/2013 20:56:50Commented Mar 29, 2013 at 20:56
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.
-
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– JBRWilkinson06/14/2012 23:56:42Commented Jun 14, 2012 at 23:56
Make()
withoutnew
because Make is capitalized and thus it assumes it is a constructornew
? 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.