If JavaScript doesn’t have classes, how do you implement inheritance? That’s probably one of the questions every developer has asked themselves. In this post, we’ll try to answer it once and for all.
In JavaScript: The Good Parts by Douglas Crockford, he talks about factory functions, which are the foundation for functional inheritance (not to be confused with functional programming).
Remember that there are three ways to implement inheritance in JavaScript:
- Delegation): Delegating behaviors to objects linked via [[Prototype]]
- Composition: Composing objects based on examples or prototypes, via Object.assign()
- Functional: Composing objects through functions.
Today we’ll talk about the third approach, but let’s take it step by step.
Functional Inheritance
As I mentioned, Douglas Crockford is the main advocate of this approach. In many of his talks, he discusses the dangers of classical inheritance and the use of this in JS.
He promotes a class-free object system, proposing the use of factory functions. These are basically functions that leverage several concepts, such as:
- Being able to return objects from a function
- Being able to create object literals
- The dynamic nature of objects
- Closures
Let’s see this in an example:
const humano = function (name) {
return {
nombre: name,
genero: '',
ciudad: 'Bogota',
decirGenero() {
console.log(this.nombre + ' mi genero es ' + this.genero)
},
decirCiudad() {
console.log(this.nombre + ' vivo en ' + this.ciudad)
}
};
}
const hombre = function (name) {
const that = humano(name);
return Object.assign({},
that,
{ genero: 'Masculino' }
)
}
const mujer = function (name) {
const that = humano(name);
return Object.assign({},
that,
{ genero: 'Masculino' }
)
}
const david = hombre('David');
const jane = mujer('Jane');
david.decirGenero();
david.decirCiudad();
jane.decirGenero();
jane.decirCiudad();
As you can see, we simply have functions that return objects. These can be generic (humano) or more specific (mujer and/or hombre). Each function returns an independent object.
But the power of factory functions doesn’t stop there. By using the concept of closures, we can have private properties.
const contador = function (salto) {
let contador = 0;
return {
siguiente() {
contador += salto;
},
ver() {
console.log(contador)
},
reset() {
contador = 0;
}
}
}
const contadorDos = contador(2);
contadorDos.siguiente()
contadorDos.ver() // 2
contadorDos.siguiente()
contadorDos.ver() // 4
contadorDos.reset()
contadorDos.siguiente()
contadorDos.ver() // 2
As you can see, we have a property (contador) that can’t be accessed from the objects created by this factory, but the methods inside it can access it thanks to closure.
It’s pretty straightforward, which is why it’s used in a large number of JavaScript libraries. For example:
- Express: when you create an application, it returns an object
- jQuery: when you use $(Selector), it returns an object
Advantages
- Simplicity: You only need a few steps to achieve inheritance.
- Free of this: We can stop worrying about this, or as shown in the examples, its usage becomes quite simple.
- Encapsulation: Objects can have private members.
- Each object is unique: Every time the function is called, it creates a new object, giving us control over mutation.
In my opinion, the main advantage of functional inheritance is simplicity, since that’s what primarily affects our entire project. I recommend reading this paper about complexity in projects.
Disadvantages
There are two big “disadvantages” that everyone mentions when it comes to functional inheritance or factory functions.
- Object creation performance is lower.
This is true, but let’s see by how much. If we run this test where we measure how long it takes to create an object using object literals, factory functions, and constructors, we get:
Constructor: 0.00002ms
Factory: 0.00004ms
Literal: 0.00001ms
As you can see, the creation speed difference is minimal, and it would only become significant when creating at least ten thousand objects (and even then it would only be about 2ms).
Another consideration is that while using a constructor function has better performance, you often need to use bind to get the desired behavior from this, which also affects performance.
Note: These values may vary depending on the engine you use and various factors. If you decide to run microbenchmarks like this one, I recommend watching this talk first.
- Memory consumption is higher.
This is also true, since each object created with a factory function will be created and stored in memory as an independent object.
But with today’s memory costs, is this disadvantage really justifiable?
Functional Inheritance vs. Classical Inheritance
An important consideration, in my opinion, is simplicity and flexibility. Taking only those two factors into account, I believe factory functions are a better choice.
But if you want to prioritize performance (micro-optimizing), the better option would be to use constructor functions (classes). This Quora answer will explain this better.
Conclusion
JavaScript provides us with an incredible inheritance system that lets us create large systems that are also flexible and maintainable.
At this point you might be thinking, “Is that it?” I asked myself the same question. The short answer is yes, and that’s what’s incredible about JavaScript — being very simple, yet powerful at the same time.
As we saw, all three types of inheritance are quite simple, but they require practice and knowing how to use them. In upcoming posts, I’ll bring examples of their implementation that will let us explore them more deeply. For now, I recommend reading this post — it’s really good and will show you the flexibility of inheritance in JS. Also, looking at library repositories and reading their code is a practice that will help you become a better developer.