Basic JavaScript Part 4: Enforcing New on Constructor Functions
As this is already the fourth blog post using the ?Basic JavaScript? theme, I guess we?re slowly getting a small blog series on our hands. Here are the links to the previous installments:
In the blog post on objects, I mentioned that there?s a general naming convention for constructor functions, using Pascal casing as opposed to the usual Camel case naming style for JavaScript functions. When following this naming convention we can make a visual distinction between a constructor function and a normal function. We want to make this distinction because we always need to call a constructor function with the new operator.
function Podcast() { this.title = 'Astronomy Cast'; this.description = 'A fact-based journey through the galaxy.'; this.link = 'http://www.astronomycast.com'; } Podcast.prototype.toString = function() { return 'Title: ' + this.title; }; var podcast = new Podcast(); podcast.toString();
Suppose that for some reason this naming convention slips our mind and we forget using the new operator when calling this constructor function. This usually leads to some nasty and unexpected behavior that is sometimes very hard to track down. What actually happens when we omit the new keyword, is that this now points to the global object (the window object when the JavaScript code is running in the browser) instead of the object that we intended to create. As a result, the properties in the constructor function are now added to the global object. This is definitely not what we want.
Rather than relying purely on a naming convention, we might want to enforce that every time a constructor function is called, this function is invoked properly using the new operator. In order to achieve this, we can add the following check to the beginning of the constructor function shown earlier:
function Podcast() { if(false === (this instanceof Podcast)) { return new Podcast(); } this.title = 'Astronomy Cast'; this.description = 'A fact-based journey through the galaxy.'; this.link = 'http://www.astronomycast.com'; } Podcast.prototype.toString = function() { return 'Title: ' + this.title; }; var podcast1 = Podcast(); var podcast2 = new Podcast();
Adding this check verifies whether this references an object created by our constructor function, and if not, the constructor function is called again but this time using the new operator.
So adding this check to all your constructor functions guarantees that these are invoked correctly using new.
Till next time.
This will actually product the same thing as what you have above:
function Podcast() {
var newPodcast = {};
newPodcast.title = ‘Astronomy Cast’;
newPodcast.description = ‘A fact-based journey through the galaxy.’;
newPodcast.link = ‘http://www.astronomycast.com’;
return newPodcast;
}
The keyword ‘new’ simply sets ‘this’ to be a new object, and then adds ‘return this’ to the end of your function. This function could then be further simplified to this:
function Podcast() {
return {
title:’Astronomy Cast’,
description:’A fact-based journey through the galaxy.’,
link:’http://www.astronomycast.com’
};
}
Either of these will produce the same result with or without ‘new’.
@Mitch
Indeed, this is not necessary when returning an object yourself. However, I don’t like this approach for bigger objects as I find that it clutters the code (except perhaps for small objects).
When you invoke Podcast() as a regular function, why do the properties (title, description, link) get set after the return new Podcast() statement? Wouldn’t the return statement exit the function?
@David
Yes, but the Podcast constructor is invoked recursivelly so this second invocation will actually set the values of the properties.
Mitch,
Actually your code is incorrect. Your code will be returning an object that is an instance of Object instead of Podcast and you will lose all prototypal inheritance.
Great technique!
But, I think it is better to throw if the user calls without a new; reinforcing correct behavior.
In my code, I’ll be writing my constructors with this as the first line:
Assert.isTrue(this instanceof Podcast);
using new to create an object has been discouraged by JavaScript guru such as Doug Crockford, for the reason that lack of “new” would introduce nasty consequences. Object.create is the way to go.