Software Engineering Blog

moon indicating dark mode
sun indicating light mode

Is "new" still considered harmful? Reflections on classes in JavaScript

July 08, 2019

Here are some random thoughts / notes about a way in which I don’t like to code in JavaScript.


Prototypal Inheritance

Before we continue, let’s quickly revisit how prototypal inheritance works.

When you access some method or property on an object that it doesn’t contain, it will search the next object up the prototype chain, and so on.

A better name for it is behaviour delegation, or objects linking to other objects, since they both very accurately describe what it’s doing.

We should note that this is not a well understood topic in the JavaScript community.

Now, on to “classes”…


They’re actually functions

Object.getPrototypeOf(class {})
// [Function]

The following are equivalent statements.

class Foo {
constructor(bar) {
this.bar = bar
}
baz() { /* ... */ }
static calculateFooConstant() { /* ... */ }
}

function Foo(bar) {
this.bar = bar
}
Foo.prototype.baz = function() { /* ... */ }
Foo.calculateFooConstant = function() { /* ... */ }

With the exception of a nice TypeError, as we’ll see below.

Also note that static functions are just defined on the constructor function as opposed to the prototype object.


new considered harmful

The new keyword turns any function into a constructor.

When it’s used, the function creates a new object, sets this to that object and returns it.

const foo() = function {
console.log(this)
}
foo() // prints `Object [global] { ... }`, returns undefined
new foo() // prints `foo {}`, returns `foo {}`

Mixing this up used to be quite the bug; people would add boilerplate to their functions to bypass it. Now, if you call a constructor (made with ES6) syntax without new, you get a nice error.

TypeError: Class constructor Foo cannot be invoked without 'new'

Regardless, after ES6 introduced the class syntax, it’s even easier to sweep prototypal inheritance under the rug, and code like JavaScript compiles to run on the JVM (not hating here; I enjoy Kotlin and Groovy).


Fragile Base Class Problem

The fragile base class problem is a problem of OOP (particularly inheritance), where superclasses cannot safely be changed without breaking subclasses

It’s even got it’s own Wikipedia page! Hence, favor composition over inheritance. Composition works great in JavaScript; look no further than Chapter 5 of Dr. Booleans Mostly Adequate Guide to Functional Programming.

Here’s an interesting conversation with the creator of Java, James Gosling, about creating a language without inheritance.


Inheritance Breaks Encapsulation

A few months ago my boss mentioned I should spare non-engineers from an absurd amount of technical detail. He was right; what I was telling them was not helping them in any way to do their jobs, and probably in some cases detracting.

That’s an example of breaking encapsulation, of spilling out internal details, rather than providing a nice clean API, as a good module should.

Inheritance in and of itself, breaks encapsulation. Subclasses inherit all of a superclasses fields. A much better modus operandi is a module deciding how to do what it needs to, and then exactly what to expose and how to expose it, vs. “is a” relationships.


Summary

The new class syntax makes it easy to forget what’s actually going on underneath, which is a Function with some behaviour delegation.

The fragile base class problem and broken encapsulation are excellent reasons to stay away from that extends keyword.

No, new is not harmful with the new class syntax, but the new class syntax doesn’t encourage a good understanding of JavaScript. It just makes it friendlier to developers coming from other languages.