JavaScript
Article
By Tim Severien

Patterns for Object Inheritance in JavaScript ES2015

By Tim Severien

A child reading in bed with a torch, with scary silhouettes cast on the wall by toys

With the long-awaited arrival of ES2015 (formerly known as ES6), JavaScript is equipped with syntax specifically to define classes. In this article, I’m going to explore if we can leverage the class syntax to compose classes out of smaller parts.

Keeping the hierarchy depth to a minimum is important to keep your code clean. Being smart about how you split up classes helps. For a large codebase, one option is to create classes out of smaller parts; composing classes. It’s also a common strategy to avoid duplicate code.

Imagine we’re building a game where the player lives in a world of animals. Some are friends, others are hostile (a dog person like myself might say all cats are hostile creatures). We could create a class HostileAnimal, which extends Animal, to serve as a base class for Cat. At some point, we decide to add robots designed to harm humans. The first thing we do is create the Robot class. We now have two classes that have similar properties. Both HostileAnimal and Robot are able to attack(), for instance.

If we could somehow define hostility in a separate class or object, say Hostile, we could reuse that for both Cat as Robot. We can do that in various ways.

Multiple inheritance is a feature some classical OOP languages support. As the name suggests, it gives us the ability create a class that inherits from multiple base classes. See how the Cat class extends multiple base classes in the following Python code:

class Animal(object):
  def walk(self):
    # ...

class Hostile(object):
  def attack(self, target):
    # ...

class Dog(Animal):
  # ...

class Cat(Animal, Hostile):
  # ...

dave = Cat();
dave.walk();
dave.attack(target);

An Interface is a common feature in (typed) classical OOP languages. It allows us to define what methods (and sometimes properties) a class should contain. If that class doesn’t, the compiler will raise an error. The following TypeScript code would raise an error if Cat didn’t have the attack() or walk() methods:

interface Hostile {
  attack();
}

class Animal {
  walk();
}

class Dog extends Animal {
  // ...
}

class Cat extends Animal implements Hostile {
  attack() {
    // ...
  }
}

Multiple inheritance suffers from the diamond problem (where two parent classes define the same method). Some languages dodge this problem by implementing other strategies, like mixins. Mixins are tiny classes that only contain methods. Instead of extending these classes, mixins are included in another class. In PHP, for example, mixins are implemented using Traits.

class Animal {
  // ...
}

trait Hostile {
  // ...
}

class Dog extends Animal {
  // ...
}

class Cat extends Animal {
  use Hostile;
  // ...
}

class Robot {
  use Hostile;
  // ...
}

A Recap: ES2015 Class Syntax

If you haven’t had the chance to dive into ES2015 classes or feel you don’t know enough about them, be sure to read Jeff Mott’s Object-Oriented JavaScript — A Deep Dive into ES6 Classes before you continue.

In a nutshell:

  • class Foo { ... } describes a class named Foo
  • class Foo extends Bar { ... } describes a class, Foo, that extends an other class, Bar

Within the class block, we can define properties of that class. For this article, we only need to understand constructors and methods:

  • constructor() { ... } is a reserved function which is executed upon creation (new Foo())
  • foo() { ... } creates a method named foo

The class syntax is mostly syntactic sugar over JavaScript’s prototype model. Instead of creating a class, it creates a function constructor:

class Foo {}
console.log(typeof Foo); // "function"

The takeaway here is that JavaScript isn’t a class-based, OOP language. One might even argue the syntax is deceptive, giving the impression that it is.

Composing ES2015 Classes

Interfaces can be mimicked by creating a dummy method that throws an error. Once inherited, the function must be overridden to avoid the error:

class IAnimal {
  walk() {
    throw new Error('Not implemented');
  }
}

class Dog extends IAnimal {
  // ...
}

const robbie = new Dog();
robbie.walk(); // Throws an error

As suggested before, this approach relies on inheritance. To inherit multiple classes, we will either need multiple inheritance or mixins.

Another approach would be to write a utility function that validates a class after it was defined. An example of this can be found in Wait A Moment, JavaScript Does Support Multiple Inheritance! by Andrea Giammarchi. See section “A Basic Object.implement Function Check.”

Time to explore various ways to apply multiple inheritance and mixins. All examined strategies below are available on GitHub.

Object.assign(ChildClass.prototype, Mixin...)

Pre-ES2015, we used prototypes for inheritance. All functions have a prototype property. When creating an instance using new MyFunction(), prototype is copied to a property in the instance. When you try to access a property that isn’t in the instance, the JavaScript engine will try to look it up in the prototype object.

To demonstrate, have a look at the following code:

function MyFunction () {
  this.myOwnProperty = 1;
}
MyFunction.prototype.myProtoProperty = 2;

const myInstance = new MyFunction();

// logs "1"
console.log(myInstance.myOwnProperty);
// logs "2"
console.log(myInstance.myProtoProperty);

// logs "true", because "myOwnProperty" is a property of "myInstance"
console.log(myInstance.hasOwnProperty('myOwnProperty'));
// logs "false", because "myProtoProperty" isn’t a property of "myInstance", but "myInstance.__proto__"
console.log(myInstance.hasOwnProperty('myProtoProperty'));

These prototype objects can be created and modified at runtime. Initially, I tried to use classes for Animal and Hostile:

class Animal {
  walk() {
    // ...
  }
}

class Dog {
  // ...
}

Object.assign(Dog.prototype, Animal.prototype);

The above doesn’t work because class methods are not enumerable. Practically, this means Object.assign(...) doesn’t copy methods from classes. This also makes it difficult to create a function that copies methods from one class to another. We can, however, copy each method manually:

Object.assign(Cat.prototype, {
  attack: Hostile.prototype.attack,
  walk: Animal.prototype.walk,
});

Another way is to ditch classes and use objects as mixins. A positive side-effect is that mixin objects cannot be used to create instances, preventing misuse.

const Animal = {
  walk() {
    // ...
  },
};

const Hostile = {
  attack(target) {
    // ...
  },
};

class Cat {
  // ...
}

Object.assign(Cat.prototype, Animal, Hostile);

Pros

  • Mixins cannot be initialized

Cons

  • Requires an extra line of code
  • Object.assign() is a little obscure
  • Reinventing prototypical inheritance to work with ES2015 classes

Composing Objects in Constructors

With ES2015 classes, you can override the instance by returning an object in the constructor:

class Answer {
  constructor(question) {
    return {
      answer: 42,
    };
  }
}

// { answer: 42 }
new Answer("Life, the universe, and everything");

We can leverage that feature to compose an object from multiple classes inside a subclass. Note that Object.assign(...) still doesn’t work well with mixin classes, so I used objects here as well:

const Animal = {
  walk() {
    // ...
  },
};

const Hostile = {
  attack(target) {
    // ...
  },
};

class Cat {
  constructor() {
    // Cat-specific properties and methods go here
    // ...

    return Object.assign(
      {},
      Animal,
      Hostile,
      this
    );
  }
}

Since this refers to a class (with non-enumerable methods) in above context, Object.assign(..., this) doesn’t copy the methods of Cat. Instead, you will have to set fields and methods on this explicitly in order for Object.assign() to be able to apply those, like so:

class Cat {
  constructor() {
    this.purr = () => {
      // ...
    };

    return Object.assign(
      {},
      Animal,
      Hostile,
      this
    );
  }
}

This approach is not practical. Because you’re returning a new object instead of an instance, it is essentially equivalent to:

const createCat = () => Object.assign({}, Animal, Hostile, {
  purr() {
    // ...
  }
});

const thunder = createCat();
thunder.walk();
thunder.attack();

I think we can agree the latter is more readable.

Pros

  • It works, I guess?

Cons

  • Very obscure
  • Zero benefit from ES2015 class syntax
  • Misuse of ES2015 classes

Class Factory Function

This approach leverages JavaScript’s ability to define a class at runtime.

First, we will need base classes. In our example, Animal and Robot serve as base classes. If you want to start from scratch, an empty class works, too.

class Animal {
  // ...
}

class Robot {
  // ...
}

Next, we have to create a factory function that returns a new class that extends class Base, which is passed as a parameter. These are the mixins:

const Hostile = (Base) => class Hostile extends Base {
  // ...
};

Now we can pass any class to the Hostile function which will return a new class combining Hostile and whatever class we passed to the function:

class Dog extends Animal {
  // ...
}

class Cat extends Hostile(Animal) {
  // ...
}

class HostileRobot extends Hostile(Robot) {
  // ...
}

We could pipe through several classes to apply multiple mixins:

class Cat extends Demonic(Hostile(Mammal(Animal))) {
  // ...
}

You can also use Object as a base class:

class Robot extends Hostile(Object) {
  // ...
}

Pros

  • Easier to understand, because all information is in the class declaration header

Cons

  • Creating classes at runtime might impact startup performance and/or memory usage

Conclusion

When I decided to research this topic and write an article about it, I expected JavaScript’s prototypical model to be helpful for generating classes. Because the class syntax makes methods non-enumerable, object manipulation becomes much harder, almost impractical.

The class syntax might create the illusion that JavaScript is a class-based OOP language, but it isn’t. With most approaches, you will have to modify an object’s prototype to mimic multiple inheritance. The last approach, using class factory functions, is an acceptable strategy for using mixins to compose classes.

If you find prototype-based programming restrictive, you might want to look at your mindset. Prototypes provide unparalleled flexibility that you can take advantage of.

If, for any reason, you still prefer classical programming, you might want to look into languages that compile to JavaScript. TypeScript, for example, is a superset of JavaScript that adds (optional) static typing and patterns you will recognize from other classical OOP languages.

Are you going to use either of above approaches in your projects? Did you find better approaches? Let me know in the comments!

This article was peer reviewed by Jeff Mott, Scott Molinari, Vildan Softic, and Joan Yin. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

  • “Creating classes at runtime might impact startup performance and/or memory usage”
    Given that this affects classes, not instances, there isn’t much memory duplication. Less than when copying things to instances. I’m less worried about startup performance and more about whether this negatively affects class-based performance optimizations. However, if more people use this pattern then you can expect engines to optimize it (if they haven’t already).

    “You must define a base class, even if it’s empty, as mixins must extend another class”
    Just use `Object` in that case.

  • Nolan Docherty

    You missed Object.create() that will give you the __proto__ without messing about with the prototypal inheritance pattern.

    function myParamPrivate(__value) {
    let privateValue = __value;
    return {
    getValue: () => privateValue,
    publicValue: []
    }
    }

    const myPrototype = {
    addToPublic (value) {
    this.publicValue.push(value);
    },
    getPublic () {
    return this.publicValue;
    }
    }

    const myOLOO = (value) => Object.assign(Object.create(myPrototype), myParamPrivate(value));

    let myFirstOLOO = myOLOO(“test”);

    console.log(“myFirstOLOO”, myFirstOLOO);

    A factory function that gives a nice clean object with a proper private variables

    I’ve
    taken this pattern to the point I can extend the prototypes and add in
    new private variables that cant access the original private variables
    but there is still a little more work I need to do to polish it all off.

    • Thank you for your comment. For this article, I wanted to look at inheritance with ES2015 class syntax specifically. Object.assign() and friends work perfectly, just not for ES2015 classes, as I tried to point out in my article.

      It all depends on what the developer prefers. If they prefer class-based OOP more than anything, this article should help. If the developer prefers functional programming or prototypal OOP, Object.create() all the way :)

  • Aleksandr Strutynskyy

    why the classes? You get objects for free in JS, just and use factory functions with Object.assign for composition instead… same idea, similar code, but no side effects, no “new” keyword and everything makes more sense.

    • I fully agree–where friends and colleague use classes, I probably prefer objects. However, that doesn’t mean we have to stop learning! I was very curious how we can achieve complex inheritance and mixins with ES2015 classes, and if they would be helpful in that regard.

      • Aleksandr Strutynskyy

        Yeah, I’m just very strongly against using “classes” in JS, since when I was just starting with JS back in 2008, I learned “incorrect” way to code (using “classes”, just constructor function back then) and it took me years to understand Javascript and completely move away from classes and “new” keyboard, I just hope newcomers understand the real javascript and what prototypes are, can save a ton of time and frustration when Javascript doesn’t behave the way they expect when coming from another language.

  • ethanresnick

    Good article, and I think the final solution is very elegant. Still, I think the claim that the non-enumerability of class methods “makes it difficult to create a function that copies methods from one class to another” is really overblown. To get a class’s methods, all you have to do is `const methods = Reflect.ownKeys(MyClass.prototype).map(methodName => MyClass.prototype[methodName])`. (If you’re worried some non-method junk may have been stuck on the prototype, you can stick in a `.filter(maybeMethod => MyClass.prototype[maybeMethod] === ‘function’)`.

    • Thanks for your great comment. Yeah, you’re absolutely right. It would work for classes, but not so much for subclasses, as it ignores properties from parent class, but very useful nonetheless. I completely forgot the Reflect API exists! Because of your comment, I also discovered you can easily assign new functions to ES2015 classes using Reflect.set(target, propertyKey, value[, receiver]), which is awesome.

      It would be great if you could write a follow-up, elaborating on the Reflect API. I’m sure SitePoint would be interested to publish it!

  • ShirtlessKirk

    One quick correction. Object.assign doesn’t do inheritance, because there’s no such thing in JavaScript. It does prototypal delegation.

  • Aleksandr Strutynskyy

    Kirk already commented on “inheritance” thing. Here is an example of using Object.assign as composition using Tim Severien previous examples:
    const Animal = () => { return { walk() {} } };
    const Hostile = () => { return { attack(target) {} } };
    const Cat = (name) => { return Object.assign({ name }, Animal(), Hostile()) };
    const myCat = Cat(‘Fluffy’);
    console.log(myCat); // { name: ‘Fluffy’, walk(), attack() }, no classes no “new” and you can keep adding functionality to the Cat factory function in order to create Cat object with whatever properties it needs.

    I highly recommend going through this list to learn JS in great details: https://github.com/canhoviet/essential-javascript-links

  • Ionut Cirja

    Avoid inheritance, use composition. What about that?

    • Search the internet! There are more than enough posts, articles, books and other resources about that, and they are way better I could ever write. Instead of bloating my article with decade-old princples and discussions, I chose to focus on ES2015 classes and multiple inheritance.

Recommended
Sponsors
Get the latest in JavaScript, once a week, for free.