Extending Multiple Classes in JavaScript

Michael P Smith
7 min readNov 23, 2020

--

Photo by Markus Spiske from Pexels

For developers coming from traditional OOP languages, I have seen them dismayed, nay, horrified that you cannot do the following in JavaScript:

class MyClass extends SomeClass, YourClass, NoClass {
// rest of class here
}

“What kind of <favorite-expletive-here> is this language” they bemoan on Reddit, or Stack Overflow, or Uncle Billy’s JS blog. “This isn’t a real language. Who designed this <other-favorite-expletive-here>?”

Of course, I just facepalm because I know the inner secrets of JavaScript. The beauty of the language. Multiple Extends? We don’t need no stinkin’ Multiple Extends!

But for all my friends who just have to have it, there is a very ingenious method of getting what you want. All it takes is a very simple function using a rather ordinary but very powerful method of the Array object — the Array.reduce() method.

Before We Begin

We will not be creating a new way to extend a class. We will not be creating a new verb in the language. We will be merely simulating multiple extends on a class. Repeat after me. Simulating. It gives you what you want, but not by creating a new JavaScript verb.

That’s out of the way. Let’s get to it!

In our example, we will be constructing animals out of different parts.

Here is what we want:

class Cat extends AnimalBase, Body, Head, Tail, Legs) {
constructor() {
super();
this.legs = 4;
}
}

Here is what it’s going to look like:

class Cat extends extender(body, head, tail, legs) {
constructor() {
super();
this.legs = 4;
}
}

As you can see, there is very little different from our code to the goal code. Only what we are extended. But the results are the same. Our animal will have the properties and methods associated with AnimalBase, Body, Head, Tail, and Legs.

The difference is in that extender() function. Instead of extending another class, we going to be extending a function that returns a class. And here’s that function:

const extender = (...parts) => parts.reduce(creator, BaseAnimal);

Wait. Really? That’s it? Yes, that’s all there is to it. It is here that the magic of the Array.reduce() method comes into play.

Array.reduce()

The Array.reduce() method iterates over an array, applying each element to a function you pass to it as the first argument in the declaration. The result of that function is then remembered from one iteration to the next and is passed to your function with each iteration. That result is called the ‘accumulator’. When all the elements are processed, the final result will be the value returned from the Array.reduce() method. I’ve done a blog post and video at my Boring JavaScript blog on the Array.reduce() method if you want more information.

In our example, ‘creator’ is the function for the Array.reduce() method, while ‘BaseAnimal’ is a pre-defined class passed as the initial value of the accumulator. ‘parts’ are the arguments passed to the function, converted to an array using a ‘rest’ operator.

Let’s take a look at that ‘creator’ function:

// 'allAnimalParts' is the accumulator
// 'animalPart' is the element of the array
const creator = (allAnimalParts, animalPart) => animalPart(allAnimalParts);

Your eyes don’t deceive you — that’s all there is to it. A simple one liner that uses the ‘current value’ of the reducer function (the element of the array) as the name of the function to call, passing to it the ‘accumulator’ of the reducer() function. The result of that is passed back to the reducer function as the ‘accumulator’ to be used for the next iteration (or to return to the calling code when the iteration is done).

And the results of that function? Why, class definitions.

The True Magic Is In The Class

Where are we now? Our reducer function is calling a function in which an object is passed to it. That reducer function returns a class definition for each element, and by design, the final value of the reducer function().

Hold on: Returns a class definition? Yes! But how?

Everything in JavaScript is an Object (well, except for simple values). An Array is an Object. A Function is an Object. And a Class Definition is an Object (a Class Definition is actually nothing more than syntactic sugar for a Function object). You can pass Objects as arguments to functions, right? Which means you can pass Arrays or Functions or … yes … even Classes.

And that’s the magic in this design. Let’s look at how a class is now designed:

function body(Base) {
class Body extends Base {
constructor() {
super();
this.body = true;
}
}
return Body;
}

Instead of ‘Body’ being a simple Class definition, we now have a ‘body’ function that returns a Class definition. And what does this class extend? It extends the argument passed to the function — which is also a Class definition.

What finally gets returned from this function is a Class that has been extended by the argument to the function — which is another class.

And Now — The Grand Finale

Let’s put all of this together.

We know that we have a function that returns a Class definition. That function takes as an argument another Class definition. And our reducer function calls the function that returns the Class definition to ultimately return that as our final Class definition. Confused? Let’s put together some code and take it step by step.

class BaseAnimal {
constructor() {
this.base = true;
}
}

const creator = (allAnimalParts, animalPart) => animalPart(allAnimalParts);
const extender = (...parts) => parts.reduce(creator, BaseAnimal);

class Cat extends extender(body, head, tail, legs) {
constructor() {
super();
this.legs = 4;
}
}
function body(Base) {
class Body extends Base {
constructor() {
super();
this.body = true;
}
}
return Body;
}
// all the other functions for header, tail, legs, etc. follow
// the same schema as 'body' above.

When we call “class Cat extends extender(body, head, tail, legs)”, we get the following flow:

  1. The function ‘extender’ is called, which uses Array.reduce() to call the ‘creator’ function on each element.
  2. The initial value for the reducer function (the ‘accumulator’) is set to the class “BaseAnimal”.
  3. ‘creator’ is executed with the ‘body’ function being called using the ‘accumulator’ as the argument. With the ‘accumulator’ being the “BaseAnimal” class, “class Body extends BaseAnimal” is returned as a class into ‘accumulator’.
  4. ‘creator’ is next executed with the ‘head’ function being called using the ‘accumulator’ as the argument. ‘accumulator’ is “class Body extends BaseAnimal”, so the ‘head’ function returns by extending Head with the ‘accumulator’ — basically — “class Head extends (Body extends BaseAnimal)”.
  5. ‘creator’ is executed with the ‘tail’ function. The return from this function is conceptually “class Tail extends (Head extends (Body extends BaseAnimal))”.
  6. ‘create’ is executed with the ‘legs’ function. The return from this function, again conceptually, is “class Legs extends (Tail extends (Head extends (Body extends BaseAnimal)))”.
  7. Finally — the ‘executor’ function returns the final value of ‘accumulator’ to our Class definition, giving us — conceptually — “class Cat extends (Legs extends (Tail extends (Head extends (Body extends BaseAnimal))))”.

As the final value returned from our ‘extender’ function is a Class definition, from JavaScript’s point of view, we are only passing one class. The construction of that class, however, is built layer upon layer, combining properties and methods, until a single class is built.

End result? Programmatically, we have this:

class Cat extends BaseAnimal, Body, Head, Tail, Legs {
// rest of class here
}

And — thus — we have done multiple extends within JavaScript.

And if you were to console.log an instance of the Cat class, you will get:

Cat { base: true, body: true, head: true, tail: true, legs: 4 }

Which is exactly what we wanted — multiple classes extended into a single class.

Conclusion

Again, we really haven’t done a ‘multiple extend’ in JavaScript — that’s simply not possible. But what we have done is simulated it by using Array.reduce() to provide us with a single Class definition that has layered other classes within it.

I hope you’re still with us at this point — it’s a lot to read. Drop me a note if you have any questions.

Addendum: Final Code:

Here is all the code I used:

class BaseAnimal {
constructor() {
this.base = true;
}
}

const creator = (allAnimalParts, animalPart) => animalPart(allAnimalParts);
const extender = (...parts) => parts.reduce(creator, BaseAnimal);

class Cat extends extender(body, head, tail, legs) {
constructor() {
super();
this.legs = 4;
}
}

class RoadRunner extends extender(body, head, tail, legs, wings) {
constructor() {
super();
this.legs = 2;
this.wings = 2;
}
}

const myCat = new Cat();
console.log(myCat);
const myRoadRunner = new RoadRunner();
console.log(myRoadRunner);
// creating a new class from the functions below
const
myArms = new (arms(BaseAnimal));
console.log(myArms);
function body(Base) {
class Body extends Base {
constructor() {
super();
this.body = true;
}
}
return Body;
}

function legs(Base) {
class Legs extends Base {
constructor(num = 4) {
super();
this.legs = num;
}
}
return Legs;
}
function arms(Base) {
class Arms extends Base {
constructor(num = 2) {
super();
this.arms = num;
}
}
return Arms;
}

function head(Base) {
class Head extends Base {
constructor() {
super();
this.head = true;
}
}
return Head;
}

function tail(Base) {
class Tail extends Base {
constructor() {
super();
this.tail = true;
}
}
return Tail;
}

function wings(Base) {
class Wings extends Base {
constructor() {
super();
this.wings = true;
}
}
return Wings;
}

--

--

Michael P Smith

Known to no one else as “TheVirtuoid”. Developer since the days of the dinosaurs. Sees programming as an art form. Loves JavaScript and helping others learn.