DEV Community

Cover image for JS Inheritance - Part 2: Factory Functions vs. Classes
PabloHoudini
PabloHoudini

Posted on

JS Inheritance - Part 2: Factory Functions vs. Classes

JS Inheritance - Part 2: Factory Functions vs. Classes

Introduction

Last time, we took a look at classes and prototypes in JavaScript. We compared constructor functions to classes and discussed how the class keyword introduced in ES6 aimed to simplify object-oriented programming in JavaScript. However, the introduction of classes did not solve all the problems associated with constructor functions. Today, we will explore different ways to declare an object in JavaScript and see if there is a need to use classes at all.

Different Ways to Declare an Object

Class

class ClassCar {
    drive() {
        console.log('Vroom!');
    }
}

console.log(typeof ClassCar); // function

const car1 = new ClassCar();
console.log(car1.drive());
Enter fullscreen mode Exit fullscreen mode

Constructor Function

function ConstructorCar() {}

ConstructorCar.prototype.drive = function () {
    console.log('Vroom!');
};

console.log(typeof ConstructorCar); // function

const car2 = new ConstructorCar();
console.log(car2.drive());
Enter fullscreen mode Exit fullscreen mode

Factory Function

const proto = {
    drive() {
        console.log('Vroom!');
    },
};

const factoryCar = () => Object.create(proto);

console.log(typeof proto); // object
console.log(typeof factoryCar); // function

const car3 = factoryCar();
console.log(car3.drive());
Enter fullscreen mode Exit fullscreen mode

Each of these strategies stores methods on a shared prototype and optionally supports private data via constructor function closures. In other words, they have mostly the same features and could mostly be used interchangeably. The question now is: you can, but should you?

Factory Functions

We talked about constructor functions. We know that the new keyword is something that changes a regular function into a constructor function. If we want to free ourselves from the confines of the classical model, we have to embrace the prototypal model. In a prototypal model, objects inherit from objects. (Do you remember Object.prototype?) However, JavaScript lacks an operator that is responsible for such an operation. Instead, it has a new keyword that can produce a new object that inherits from Object.prototype. This was done on purpose to make it look familiar to classically trained programmers, but it failed miserably. It simply did not appeal to the classical crowd. Also, it obscured JavaScript from its real inheritance model.

Should You Use Factory Functions Instead of Classes?

Much like Array, class is not a language feature; it’s syntactic obscurantism. It tries to hide the prototypical inheritance model and the clumsy idioms that come with it, and it implies that JavaScript is doing something that it is not. So, by design, there are no classes. However, JavaScript has all the necessary features to implement OOP; there was no need to add classes in ES6. It was added just so Java or C# developers can feel comfortable.

To put a fine point on that, a child of a prototype isn’t a copy of its prototype, nor is it an object with the same shape as its prototype. The child has a living reference to the prototype, and any prototype property that doesn’t exist on the child is a one-way reference to a property of the same name on the prototype.

Most of the time, classes in JavaScript don’t serve a good purpose; they are not really useful.

Why We Should Not Use Classes

Much like Array, class is not a language feature; it’s syntactic obscurantism. It tries to hide the prototypical inheritance model and the clumsy idioms that come with it, and it implies that JavaScript is doing something that it is not. So, by design, there are no classes. However, JavaScript has all the necessary features to implement OOP; there was no need to add classes in ES6. It was added just so Java or C# developers can feel comfortable.

To put a fine point on that, a child of a prototype isn’t a copy of its prototype, nor is it an object with the same shape as its prototype. The child has a living reference to the prototype, and any prototype property that doesn’t exist on the child is a one-way reference to a property of the same name on the prototype.

Most of the time, classes in JavaScript don’t serve a good purpose; they are not really useful.

Functional Programming for Life

In JavaScript, functions are first-class citizens. Functional programming is all about using functions to their fullest extent. Functional programming has come and gone and come back again. A couple of reasons why, in my humble opinion, we should try to understand the benefits of functional programming. One should just ask himself if factories are much more flexible than either constructor functions or classes, and they don’t lead people down the wrong path by tempting them with the extends keyword and deep inheritance hierarchies. There are many safer code reuse mechanisms you should favor over class inheritance, including functions and modules.

Some Code to Prove It

So we could start with something like this:

// class
class ClassCar {
    drive() {
        console.log('Vroom!');
    }
}

console.log(typeof ClassCar); // function

const car1 = new ClassCar();
console.log(car1.drive());
Enter fullscreen mode Exit fullscreen mode

And a little refactor using modules:

// Car.js

export function drive() {
    console.log('Vroom!');
}
export function stop() {
    console.log('Stopping');
}

// app.js

import * as car from './Car';

car.drive();
car.stop();
Enter fullscreen mode Exit fullscreen mode

So we got rid of two keywords here: new and class. As we see in the above example, we do not really need them to achieve what we want to achieve. Another example would be:

class Child extends Parent {}

// instead we could do

const fun = parent(child());
Enter fullscreen mode Exit fullscreen mode

Here we also saw another keyword extends that has come and gone. Do we really need it? Answer: not really.

A real-life example utilizing such a programming style would be the Fastify framework for Node.js backend development.

Moreover, on the frontend, React has also ditched the old concept of React.Component and has moved towards functional components and hooks.

Summary

Classes in JavaScript make things look more familiar to classically trained developers. However, it is only a sugar coating, and when uncovered, it reveals JavaScript's true nature. And the true nature of JavaScript is prototypical inheritance. So, if you are not one of the developers coming from OOP programming languages like Java or C#, then I strongly suggest stopping using classes and favoring objects and functions, and moving on to modules. This will be extremely beneficial if you want to work in frameworks like React or Fastify. JavaScript, like any other language, has good and bad parts. Surely, one of the best parts is the lack of classes and class inheritance. It might take some effort and time to truly master prototypal inheritance, but it is surely worth it.

Top comments (0)