2013-07-22

In defense of JavaScript’s constructors

JavaScript’s constructors have never been particularly popular: Douglas Crockford doesn’t like them and recently, more anti-constructor material has been published (two examples: blog posts by Kyle Simpson and Eric Elliott).

In this blog post, I explain that not all of the constructors’ flaws are real. But even with those that are real, I still recommend to use them. I’ll tell you why and what the future holds for constructors.

Recommendations

For ECMAScript 5, I recommend to:
  • always use constructors
  • always use new when creating an instance
The main advantages of doing so are:
  • Your code better fits into the JavaScript mainstream and is more likely to be portable between frameworks.
  • Speed advantages. In modern engines, using instances of constructors is very fast (e.g. via hidden classes).
  • Classes, the default inheritance construct in ECMAScript 6 (see below), will be based on constructors.
There are a few arguments against using constructors and new in the manner described above. The following sections answer those arguments.

Why not make new optional?

If you use constructors, there is always a danger of forgetting new. A simple pattern allows you to call a constructor either as a function or as a constructor, eliminating that danger:
    function Foo(arg) {
        if (!(this instanceof Foo)) {
            return new Foo(arg);
        }
        this.arg = arg;
    }
The main reason against this pattern is consistency: you should either always use new or never. A mix makes your code less readable. Thankfully, there are other ways of protecting yourself against forgetting new (described below). Those ways have the added benefits of being simpler than the above pattern.

But built-in constructors allow me to omit new

For some built-in constructors, it doesn’t matter whether you use new or not. For example, you can invoke the array constructor as a function.
    > new Array(3).length
    3
    > Array(3).length
    3
Similarly, the function Object converts non-objects to objects and returns objects unmodified. This constructor behaves the same with new (which unfortunately runs counter to the semantics of new):
    > var obj = {};
    > Object(obj) === obj
    true
    > new Object(obj) === obj
    true
However, there are also several built-ins where new produces different results from a function call:
    > typeof String('abc')
    'string'
    > typeof new String('abc')
    'object'

    > typeof Date()
    'string'
    > typeof new Date()
    'object'

But I want to spread

What if you want provide the arguments to a constructor invocation via an array? In ECMAScript 6, there will be the so-called “spread” operator (...) for doing so:
    new Date(...[2011, 11, 24]) // Christmas Eve 2011
In ECMAScript 5, you can use apply() for spreading, but it only works with functions, not with constructors. This is indeed an argument for making a constructor work without new. However, it is relatively easy to implement a tool function that spreads while invoking a constructor via new (for details, see [1]):
    > Date.construct([2011, 11, 24])
    Sat Dec 24 2011 00:00:00 GMT+0100 (CET)

Protecting against accidentally omitting new

Protection via strict mode

The easiest way to protect against accidentally omitting new is to write your JavaScript in strict mode [2]. To see what strict mode protects you from, let’s look at the following code, which is not in strict mode.
    function Color(name) {
        this.name = name;
    }
If we now create an instance of Color and omit new, things fail silently, without a warning: the result is undefined which will lead to problems later on.
    > var c = Color('green');
    > c
    undefined
Additionally, we have just accidentally created the global variable name, because in non-strict mode, this points to the global object (window in browsers) during a function call.
    > name
    'green'
The strict mode version of Color looks like this:
    function Color2(name) {
        'use strict';
        this.name = name;
    }
In strict mode, this is undefined during function calls. Which is why we now get a warning:
    > var c2 = Color2('green');
    TypeError: Cannot set property 'name' of undefined
You may be tempted to throw an exception if a constructor is called as a function:
    function Color2(name) {
        'use strict';
        if (this === undefined) {
            throw new Error('Constructor called as a function');
        }
        this.name = name;
    }
However, this still does not protect you from forgetting new if you use a namespace (because this will refer to the namespace when you do so):
    > var namespace = {};
    > namespace.Color2 = Color2;

    > namespace.Color2('green') // no exception!
    undefined
    > namespace.name
    'green'

Protection via lint tools

Alas, none of the popular lint tools (JSLint, JSHint) do this, but you could statically complain if a function is called whose name starts with a capital letter (similar to the tools already checking that you don’t apply new to a function whose name starts with a lowercase letter).

But constructors can only produce direct instances

In many object-oriented languages, constructors can only produce direct instances. For example, lets assume you want to implement the class Expression in Java that has the subclasses Addition and Multiplication. If parsing could produce direct instances of the latter two classes, you couldn’t implement it is a constructor of Expression, because that constructor could only produce direct instances of Expression. As a work-around, static factory methods are used in Java:
    class Expression {
        public static Expression parse(String str) {
            if (...) {
                return new Addition(..);
            } else if (...) {
                return new Multiplication(...);
            } else {
                throw new ExpressionException(...);
            }
        }
    }
    ...
    Expression expr = Expression.parse(someStr);
Dart has factory constructors for this purpose. In JavaScript, you can simply return whatever object you need from a constructor [3]. Thus, the JavaScript version of the above code would look as follows.
    function Expression(str) {
        if (...) {
            return new Addition(..);
        } else if (...) {
            return new Multiplication(...);
        } else {
            throw new ExpressionException(...);
        }
    }
    ...
    var expr = new Expression(someStr);
This is good news: JavaScript constructors don’t lock you in, you can always change your mind as to whether a constructor should return a direct instance or something else.

Can‘t we do better than constructors?

Yes, we can do better than constructors. The core of JavaScript inheritance is prototypes, a relationship between objects. Constructors add unnecessary complexity to these basics. I’ve always thought that an instance prototype is a better instance factory than a constructor function. My proof-of-concept library Proto.js explores this idea.

Alas, we’re stuck with constructors. So far, the JavaScript community has not agreed on a common inheritance library (which would help tooling and code portability) and it is doubtful that that will ever happen. That means, we’re stuck with constructors under ECMAScript 5.

Is there no way to at least ease some of the constructor pain?

We may be able to agree on a common library if it is minimal. I don’t think more radical approaches such as Proto.js have a chance of being universally adopted.

The most pressing constructor pain point is subclassing. Node.js has the utility function util.inherits() that only tackles subclassing, nothing else (e.g., it does not help with defining constructors). I’d love this function to be ported to browsers (preferably via a UMD module) and gain widespread use everywhere.

ECMAScript 6: the future of constructors

The largest benefit of ECMAScript 6 (ES6) classes [4] is that they unite the community. They will become the de jure and de facto standard for inheritance in ECMAScript 6 and later.

ES6 classes internally desugar to constructors. This is not optimal, because the sugared version looks quite different from the desugared version. That is bound to surprise people in the future when they are trying to find out how classes actually work. In contrast, prototypal inheritance is a much better match for the structure of classes. Hence, desugaring them to something prototypal would have been cleaner.

On the other hand, backward compatibility is a strong reason in favor of constructors. And one of the main goals for classes was to make them as lightweight as possible. Therefore, even though I’m not completely happy with classes, I think they are the best currently possible solution and an important part of ES6 that deserves everyone’s support.

Don’t ES6 classes prevent multiple inheritance?

No, they don’t. ECMAScript will probably support multiple inheritance after ECMAScript 6 (via traits, mixins, etc.). Until then, classes provide a hook for inheritance libraries: In addition to a constructor, a class can also extend an object. In the latter case, the object becomes the prototype of the class prototype. That allows you to, say, write a utility function mixin that builds a prototype chain for the class prototype. For example:
    class Sub extends mixin(Super, Mixin1, Mixin2) {
        ...
    }
An instance subInstance of Sub would have the following prototype chain.
    Super.prototype
      ↑
    Mixin1, Mixin2 (merged)
      ↑
    Sub.prototype
      ↑
    subInstance

Conclusion

Ideals may tell us something important about what we would like to be. But compromises tell us who we are.
— Avishai Margalit
Under ECMAScript 5, we have many inheritance APIs, leading to reduced portability of code. If your framework doesn’t force you to use an inheritance API, it is best to use vanilla constructor functions, possibly along with a utility for handling subclassing.

Under ECMAScript 6, using classes [4] is the obvious choice. They help with subclassing, let you subclass built-in constructors [5] and more.

Both solutions are compromises, but especially classes will make JavaScript a friendlier language and unify a currently very divided inheritance landscape. Many custom inheritance APIs have been created to help with data binding. ECMAScript 7 will remove this last reason for custom APIs via built-in support for data binding, via Object.observe().

References

  1. Spreading arrays into arguments in JavaScript
  2. JavaScript’s strict mode: a summary
  3. Exemplar patterns in JavaScript
  4. ECMAScript.next: classes
  5. Subclassing builtins in ECMAScript 6

No comments: