The Factory Pattern in JavaScript
We could also call this article, “JavaScript Constructors Considered Harmful.”
I recall reading a post recently which describes factory methods as a best practice. I can’t tell you how thrilled this made me as I’ve been advocating this for years, even to the extreme view that the author used as an example: new Form().
In my most recent talk at AJAXWorld 2009, I briefly discussed the Factory Pattern as it applies to JavaScript. In my talk, I took a jump away from simple best practices and gave a real-world problem/solution approach to factories in JavaScript.
The problem: JavaScript will allow you to invoke constructor functions as normal functions.
The simplest possible solution: Use a factory method to instantiate everything.
My personal recommendation is to only use constructors internally and to never expose constructors directly.
What the hell? Why are constructors so bad?
Constructors in JavaScript are bad because of one thing, and it’s the same thing that is at the heart of almost all of JavaScript’s problems: implied global scope.
Consider the following code:
// Define our constructor function
var Person = function (name, location) {
this.name = name;
this.location = location;
};
// Create one instance of Person
var mike = new Person('Mike G.', 'NYC');
// Create another instance of Person
var alex = Person('Alex H.', 'NYC');
Did you spot my mistake? I omitted the new keyword while creating the second Person instance. If you run this code in any web browser, you’ll notice that the browser will redirect you immediately upon running it.
This is a big problem.
When you invoke any function with the new keyword, it treats it as a constructor and executes within the context of the newly created instance. When you invoke any function without the new keyword, it treats it as a normal function and executes within the context in which the function was defined (typically window).
With that knowledge, go back and re-read the code and tell me why the browser redirects now…
Factories save the day
The easiest way to fix this problem is to use a factory. Here’s my proposed solution:
var Person = function (name, location) {
return Person.factory(name, location);
};
Person.factory = function (name, location) {
return new Person(name, factory);
};
One of JavaScript’s brilliant features is that Constructors can have return values. In this example, my constructor is returning the result of the Person.factory call.
Now it doesn’t matter how you invoke this constructor. You can use new or you can omit it. This is defensive code and it’s JavaScript done right.