JavaScript Constructor Functions vs Factory Functions

Uncategorized 5 5 Comments

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:

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(). If you return anything truthy (except an object) you’ll get back undefined. Those are just weird JavaScript trivia edge cases, though, so they don’t count.

The point is, you can return any arbitrary object you want, just like you can with a factory function. Try it out:

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 not new is required. In my opinion, it shouldn’t be. You’ve effectively short circuited the new 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 flexible this context.

What is a factory?

Simple – just a regular function that returns a new object. It behaves just like any other function would:

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 make this behave badly, see next point).

  • this behaves as it normally would – so you can use it to access the parent object (for example, inside player.create(), this refers to player, just like any other method invocation would. call and apply also reassign this, 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(); or var 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 to foo – just like every other JavaScript method — see benefits).

Conclusion

Stop Using Constructor Functions in JavaScript


  1. Sylvain Pollet-Villard - February 16, 2013

    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 :

    this behaves as it normally would

    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.

    • Eric Elliott - February 17, 2013

      instanceof, Object.constructor, isPrototypeOf, getPrototypeOf"

      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

  2. keripix - June 14, 2013

    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?

    • Eric Elliott - June 18, 2013

      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. =)




Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" cssfile="" class="" title="" data-url=""> <span class="" title="" data-url="">