My previous blog post, Stop Using Constructor Functions in JavaScript has been a bit controversial, so I decided to break down the benefits vs drawbacks (or pros and cons if you prefer). Since everybody is already familiar with constructors, we’ll start there.
If you need more information and examples about factory functions, click the link above. There are also a lot more examples of factory function use in the source code from my talk, Fluent JavaScript Part One: Prototypal OO. I hope this sheds more light on the subject.
What is a Constructor?
Before we go any further, it’ll probably help to review the basics of the constructor function.
They’re pretty simple:
function Foo() { this.bar = 'baz'; }
The call signature looks something like this:
myFoo = new Foo();
The capital letter doesn’t have any meaning to JavaScript. It’s just there to alert you that you’re calling a constructor, so you don’t forget new
and break something. The special keyword, new
is what matters. It sets the context of this
to mean the newly created object (an instance of Foo()
).
That is what this
is all about. (See what I did there?)
Other than that, the constructor is just like any other function. Almost. If you return something falsy you’ll get back a new, empty instance of Foo()
instead of the value you returned. However, if you return any object, including a function or array, you’ll get back whatever you returned. (Hat tip: Chris Hernandez)
The point is, you can return any arbitrary object you want, just like you can with a factory function. Try it out:
function Foo() { this.bar = 'baz'; return { baz: 'bif' }; } console.log(new Foo()); // Object {baz: "bif"}
Notice how it returns the object literal instead of `this`? Cool, huh? A constructor is just a factory function with `this` glued to the new object for your “convenience”. In other words, it’s a less flexible subset of a factory function.
The problem is, it’s not really convenient. Read on!
Benefits of using constructors
-
Most books teach you to use constructors and
new
-
this
refers to the new object -
Some people like the way
var myFoo = new Foo();
reads.
Drawbacks
-
Details of instantiation get leaked into the calling API (via the
new
requirement), so all callers are tightly coupled to the constructor implementation. If you ever need the additional flexibility of the factory, you’ll have to refactor all callers (admittedly the exceptional case, rather than the rule). -
Forgetting
new
is such a common bug, you should strongly consider adding a boilerplate check to ensure that the constructor is called correctly (if (!(this instanceof Foo)) { return new Foo() }
). -
If you do the
instanceof
check, it leaves ambiguity as to whether or notnew
is required. In my opinion, it shouldn’t be. You’ve effectively short circuited thenew
requirement, which means you could erase drawback #1. But then you’ve just got a factory function in all but name, with additional boilerplate, a capital letter, and less flexiblethis
context.
What is a factory?
Simple – just a regular function that returns a new object. It behaves just like any other function would:
function foo() { return { bar: 'baz' }; }
For more examples, check out the Prototypal OO source code mentioned already.
Benefits of using factories
-
Less code – no boilerplate required.
-
You can return any arbitrary object, and use any arbitrary prototype – giving you more flexibility to create various types of objects which implement the same API. For example, a media player that can create instances of both HTML5 and flash players, or an event library which can emit DOM events or web socket events.
-
You’d never have a need to convert from a factory to a constructor, so refactoring will never be an issue.
-
No ambiguity about using
new
. Don’t. (It will makethis
behave badly, see next point). -
this
behaves as it normally would – so you can use it to access the parent object (for example, insideplayer.create()
,this
refers toplayer
, just like any other method invocation would.call
andapply
also reassignthis
, as expected. If you store prototypes on the parent object, that can be a great way to dynamically swap out functionality, and enable very flexible polymorphism for your object instantiation. -
No ambiguity about whether or not to capitalize. Don’t. Lint tools will complain, and then you’ll be tempted to try to use
new
, and then you’ll undo the benefit described above. -
Some people like the way
var myFoo = foo();
orvar myFoo = foo.create();
reads.
Drawbacks
-
new
doesn’t behave as expected (see above). Solution: don’t use it. -
this
doesn’t refer to the new object (instead, if the constructor is invoked with dot notation or square bracket notation, e.g. foo.bar() –this
refers tofoo
– just like every other JavaScript method — see benefits).
Pingback: Fluent JavaScript – Three Different Kinds of Prototypal OO | Eric Elliott - JavaScript Architect (A JavaScript Blog)
I think you omit major drawbacks of using factory functions: this makes many native Object methods, attributes and operators completely useless nay misleading, such as instanceof, object.constructor, isPrototypeOf, getPrototypeOf…
I consider more factory functions as a way to bring more flexibility to constructors when needed, ie more a wrapper than a replacement :
I guess by “normally” you mean “the way I think it should behave”. Kinda subjective. “this” has several meanings in Javascript and in my opinion, in the context of a function dedicated to build an object it should refer to this object.
1) You need to be careful with all of these methods, because they don't work as expected across execution contexts -- for example, iframe boundaries. That's one of the many reasons that ducktyping (aka object feature detection) is so popular in JavaScript.
2) I rarely find myself reaching for any of those tools anyway, probably as a consequence of using prototypal OO heavily. See Fluent JavaScript: Three Different Kinds of Prototypal OO
After reading your fluent-javascript.js file, I realise I’m one of the of those who are using “the old n busted” ways. So, after following your approach, I got stuck on how to mock for example barPrototype.open?
I can mock easily, for example, if I just return the barPrototype.
So, what’s your advice on this situation?
I’m a little confused by your question. You can still pass the prototype around and do whatever you like with it. If you’d like to share some sample code on jsfiddle.net, that might be useful. =)
In your fluent-javascript.js you use anonymous functions for the membership-prototype and named functions for the availability-prototype. Am I interpreting it correct when I think the named ones are actually defined in the “outer” scope and thereby shared among the instances created? And the anonymous ones are then directly attached to each new instance, and thereby not shared?
Those are unrelated concepts. I should have used named functions for all of them. The differences with named functions are 1) you can refer to the function by name from within the function (for recursion), and 2) the name will show up in your call stack while you’re debugging. You can also search for the function name with tools like tracegl ( http://trace.gl ).
What about performance implications?
Does the modern browser JIT optimize both methods in the same way?
As I’ve mentioned before: The fastest way to build an instance of an item is to return an object literal. The fastest way to attach a prototype to an object is `new`. Factories are slower.
Likewise, `new` and constructors get optimized at property access time if you generate thousands of similar items from a constructor, and the properties mostly don’t get mutated. You can think of it like a hidden class that is eventually trusted and promoted for faster access.
However, the performance differences aren’t very remarkable. All of the methods across the board are very fast (tens of millions of operations per second on a modern computer).
At these micro scales, the performance difference has to be orders of magnitude different in order to warrant a second thought by an application developer (it isn’t).
My recommendation is that you don’t even think about it unless you have a situation where you’re creating potentially millions of objects in a tight loop, and you have specifically identified object creation as a bottleneck. As for the property access thing — I wouldn’t worry about that *at all* as a software developer. The impact of the optimization at the application level is just so small that the impact of the decision will get buried under the noise of request latencies, algorithm choices, and other considerations that have a much bigger impact on your application performance. See my blog post: You’re Optimizing the Wrong Things.
Great article. I’m convinced that getting away from classical inheritance clarifies and simplifies JavaScript development, so I’m super stoked the work your doing on this blog (and in conferences) to help the cause 🙂
Just a side note, in your paragraph where you mention the weird JavaScript edge cases: when using new to invoke a constructor – unless you explicitly return an object from a constructor – you’ll always get a new instance, not undefined.
oops, forgot the citation: http://es5.github.io/#x13.2.2
Thanks!