Let us say I have the following object constructor:
function Foo(bar) {
this.bar = bar;
}
If I run the function in the global scope without the new
keyword then bar
will be set in whatever scope Foo()
is called in:
var foo = Foo(42);
console.log(bar); // 42
console.log(foo.bar); // ERROR
So my idea is to do something like this:
function Foo(bar) {
if(!(this instanceof Foo)) {
// return a Foo object
return new Foo(bar);
}
this.bar = bar;
}
That way if I do new Foo(42)
or Foo(42)
, it would always return a Foo
object.
Is this ever a good idea? If so, when? When (and why) would it be wise to avoid this technique?
Let us say I have the following object constructor:
function Foo(bar) {
this.bar = bar;
}
If I run the function in the global scope without the new
keyword then bar
will be set in whatever scope Foo()
is called in:
var foo = Foo(42);
console.log(bar); // 42
console.log(foo.bar); // ERROR
So my idea is to do something like this:
function Foo(bar) {
if(!(this instanceof Foo)) {
// return a Foo object
return new Foo(bar);
}
this.bar = bar;
}
That way if I do new Foo(42)
or Foo(42)
, it would always return a Foo
object.
Is this ever a good idea? If so, when? When (and why) would it be wise to avoid this technique?
Share Improve this question edited Dec 31, 2013 at 17:21 Shog9 160k36 gold badges235 silver badges240 bronze badges asked Dec 31, 2013 at 16:06 qwertynlqwertynl 3,9331 gold badge23 silver badges43 bronze badges 15- 1 Have you ever heard anyone make a case against it? – cookie monster Commented Dec 31, 2013 at 16:11
- @cookiemonster Yes. Something along the lines of "Why would you do that?" "You are ruining the sanctity of javascript!" "Why would you force an object on someone when they don't want it?" – qwertynl Commented Dec 31, 2013 at 16:13
- None of those are cases against it. They appear to be the rantings of irrational minds. Have you heard an actual case? I can't imagine why such a guard would be undesirable. – cookie monster Commented Dec 31, 2013 at 16:15
- "Is this ever a good idea?" Doesn't your question itself demonstrate when/why it's a good idea? – cookie monster Commented Dec 31, 2013 at 16:17
- 2 @cookiemonster it is a good "idea" in my case, but other people might not agree and I would like to know why and in what case. – qwertynl Commented Dec 31, 2013 at 16:17
4 Answers
Reset to default 8This can be useful for when you would like to internally construct an object wrapper.
A little known library internally uses this approach, jQuery.
They do not use the instanceof
approach any longer though. Every time jQuery is called, it automatically does this:
// Define a local copy of jQuery
jQuery = function( selector, context ) {
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init( selector, context );
}
Everything it does internally references this local copy. At the very end of its work, it then attaches it to the global scope
window.jQuery = window.$ = jQuery;
So every time you call $()
it internally uses new
. It is also assuming that you do not use new
externally, but it really doesn't care if you do or not.
edit
jsFiddle Demo
//Foo entrance
function Foo(bar){
//construct a new Foo object to return
return new Foo.prototype.build(bar);
}
//constructor for returning new prototype
Foo.prototype.build = function(bar){
//initialize base settings
this.bar = bar;
//chain object
return this;
};
//this is the complex part
//tie the prototype of Foo.prototype.build.prototype to Foo.prototype
//so that is "seems" like Foo is the parent of the prototype assignments
//while allowing for the use of returning a new object in the Foo entrance
Foo.prototype.build.prototype = Foo.prototype;
//simple expansions at this point, nothing looks too different
//makes a function for a constructed Foo that logs hello
Foo.prototype.hello = function(){
console.log("Hello "+this.bar+" :)");
//returns this for chaining
return this;
};
//more extensions, this one changes this.bar's value
Foo.prototype.setBar = function(arg){
//accesses the current Foo instance's .bar property
this.bar = arg;
//returns this for chianing
return this;
};
//should be seeing a pattern
Foo.prototype.goodbye = function(){
console.log("Bye "+this.bar+" :(");
return this;
};
var foo = Foo(42);
//console.log(bar); // ERROR
console.log(foo.bar); // 42
foo.hello(); //Hello 42 :)
foo.hello().setBar(9001).goodbye(); //Hello 42 :) Bye 9001 :(
Foo(12).hello(); //Hello 12 :)
While I don't have anything against that style, I would not personally use it just to be consistent. Sure, I can make all my constructors like this but it would seem like a lot more code to write.
If I'm worried about accidentally invoking a constructor without new
, I would rather use JSHint to warn me about it. See http://www.jshint.com/docs/options/#newcap.
I have one good reason to avoid it: if you're not forgetting to use new
, then it's just unnecessary overhead. You may never notice this unless you're creating thousands of instances of a given object, but it's still there.
I recommend limiting the number of places in your code where new
-based object creation is done - if you're not relying on it every place you need a new object, then you don't have to worry about forgetting it in one of them. There are numerous patterns that can help you here, but the simplest one is to just make one function somewhere that's responsible for object creation, and defer to that every time - whether it creates 1 instance or 1,000,000 instances is up to you.
That said, if this doesn't make sense then this sort of guard is an excellent fallback. Note that it bears a lot of similarity to how JavaScript's built-in type constructors double as type converters when used without new
- so I would consider user-defined types as a particularly apt place to use this technique.
See also: Is JavaScript's "new" keyword considered harmful?
Advocates of "readability" might describe it as an anti-pattern but you know your own code better than anyone else - if it works for you then go for it. Personally I'd rather have one way of doing one thing - it makes it clear and concise. If another developer enters the fray and tries to use my object with out instantiating it I'd rather it throw an error to tell him/her they are being daft.