In my previous blog post on the subject of constructor functions, I mentioned that there is a convention in JavaScript to name constructor functions with an initial capital letter and ordinary functions with an initial lowercase letter. This way, since the language doesn’t stop you from using a constructor as an ordinary function, there is at least some kind of “warning” that you may be using a constructor and therefore need to use new
.
The biggest problem is, if you do miss the new
keyword, you are going to be corrupting the global object. Is there a way to write the constructor in order to avoid this? It turns out that there is.
Normally, when you write a constructor function you don’t return the object being constructed. The JavaScript language assumes that the object created by the use of the new
keyword (called this
within the function) is the object that’s going to be returned. It turns out that you can circumvent this feature by explicitly returning the object. In other words:
var Person = function (lastName, firstName) { this.lastName = lastName; this.firstName = firstName; };
is interpreted in exactly the same manner as:
var Person = function (lastName, firstName) { this.lastName = lastName; this.firstName = firstName; return this; };
In fact, it turns out that, providing that it is an object, you can return anything from a constructor. So, as a completely gratuitous duck-typed example:
var Person = function (lastName, firstName) { return { lastName : lastName, firstName : firstName }; }; var p = new Person("Bucknall", "Julian"); console.log(p instanceof Person); // prints false
In other words, the object being returned from the Person
constructor is not an instance of Person
at all (it just looks like one). The object created by the new
keyword has been completely ignored (and it will be automatically garbage-collected later).
So what, you may be asking. What loony would return a completely different object than the constructor purports to create? Exactly, unless you know that the this
object in the constructor is already an instance of the constructor. Take a look at this (pun intended):
var Person = function (lastName, firstName) { console.log(this instanceof Person); // prints true // other code }; var p = new Person("Bucknall", "Julian");
In other words, by the time the constructor function has started to execute when called by new
, JavaScript has already patched up the this
object’s constructor
and internal prototype properties. Although the object has not been completely constructed at the start of the function, it is of the correct type.
From that knowledge and the fact we can return any old object from a constructor, we get this:
var Person = function (lastName, firstName) { if (!(this instanceof Person)) { return new Person(lastName, firstName); } this.lastName = lastName; this.firstName = firstName; }; var p = new Person("Bucknall", "Julian"); console.log(p instanceof Person); // prints true var p = Person("Bucknall", "Julian"); console.log(p instanceof Person); // prints true
In other words, we can easily write a constructor that returns a properly constructed object, whether we use the new
keyword or not.
Now playing:
Deacon Blue - Last Night I Dreamed of Henry Thomas
(from Whatever You Say, Say Nothing)
4 Responses
#1 Will Alexander said...
17-Feb-11 11:10 AMGreat tip! It is too easy to leave the new out and when you do, it may generate errors far removed from the constructor.
Is there a way you can use
apply()
so it will pass in arguments of arbitrary length? Would seem to apply (punny!) when chaining constructor calls and the parent expects more params than your subclass needs.This would fail the instanceof check, but seem to work otherwise
#2 jmweb said...
01-May-11 7:44 PMInstead of using instanceOf can't we just make sure that this does not refer to the global object?
Something like if(this === window){return new Person();}
#3 julian m bucknall said...
02-May-11 6:46 AMjmweb: Indeed, if you are writing JavaScript just for the client, you could test "this" against "window". (On the server, there is no global object property called "window", so you'd have to do something else.)
Cheers, Julian
#4 julian m bucknall said...
02-May-11 7:17 AMWill: (Sorry, somehow I missed your comment before.) An interesting question and something I didn't consider. Let's suppose that the constructor function is defined in such a way that there are optional parameters. How would you write the function such that if it's not invoked via "new" you could invoke it internally so that all the parameters are passed in. At first blush you can't use apply/call since they don't set up the new object correctly. Your idea of passing in an empty object (and presumably altering the code to check for an empty object) might work. I'll have to think on it.
Cheers, Julian
Leave a response
Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.
_emphasis_
**strong**
[text](url)
`IEnumerable`
* an item
1. an item
> Now is the time...
Preview of response