OOP in JavaScript. How it works?
Heeey Heey folks! I hope you all are doing great. In this article I want to talk about how the concept of OOP (Object Oriented Programing) works in JavaScript since it isn’t a OOP language such as Java or C#.
JavaScript is a multi-paradigm language which uses the imperative/procedural paradigm and also the functional programming paradigm. They play a key part on how the language works.
The language did not have classes at all before they were added to the language specification in 2015, known as ES6, but allowed for object-oriented programming using prototype-based inheritance. JavaScript is still a prototype-based language, even though a class keyword
is available nowadays.
For us to understand how this all work we need to go back in time when there were no classes.
Functional Programming Paradigm
This paradigm produces programs by composing and applying functions. A function is a first class citizen, meaning that they can be bound to a name, passed as arguments and returned from other functions. This is one of the main characteristics of the JavaScript language.
Constructor Function
In JavaScript, almost everything is an object. So, when we wanted to create a template (blueprint) we would use a constructor function which is basically a function that looks like this:
function Car(name, logo) {
this.name = name;
this.logo = logo;
}
const myCar = new Car('ferrari', 'horse');
myCar.name;
//=> ferrari
It’s a convention that the function name should start with a capital letter. Instances (objects) are derived from this template (blueprint) using the new keyword
.
The Prototype
All objects in JavaScript have a prototype. The prototype is another object that the original one inherits from. In others words, the original object has access to all of its prototype’s methods and properties. So, every prototype
object inherits from Object.prototype
. Let’s see an example here:

The Prototype Chain
This is a key concept on how inheritance works in JavaScript. When you try to access any property (field or method) of an object, JavaScript will first check if that property exists on the object itself. If not, it continues to look for the property in the object referenced by the prototype
. Let’s have a look on an example:
function Car(name, logo) {
this.name = name;
this.logo = logo;
}
Car.prototype.addGas(quantity) {
// ...
}
const myCar = new Car('ferrari', 'horse');
myCar.addGas(10);
myCar.toString();
If we inspect the myCar
object, we would not find the function addGas()
and toString()
defined on it. So, when the myCar.addGas()
function is called, what JavaScript does is to look on the Car.prototype
to check if the property exists there. Since addGas()
is defined on the Car.prototype
the search ends and the function is run successfully. Now, let’s see what happens when the myCar.toString()
function is called. First, JavaScript will look on the Car.prototype
, but it will not find the function there. So, it will try to find this property on the next layer (next prototype in the chain), which is the Car.prototype.prototype
that references the Object.prototype
(the prototype property of the Object constructor function) that every object on JavaScript inherits from by default, and it contains general methods that are available for all JavaScript objects, such as toString()
. In summary, that is how the prototype chain works.
Instance fields
Inside the constructor function we use the this
keyword which represents the new object that will be created via new
. this
will be automatically returned from the constructor function when it is called with new
. That means we can add fields to the new instance by adding them to this
in the constructor function.
function Car(name, logo) {
this.name = name;
this.logo = logo;
this.emptyFuel = false;
}
const myCar = new Car('ferrari', 'horse');
myCar.emptyFuel
//=> false
myCar.logo
//=> horse
Instance methods
Methods are added via the prototype
property of the constructor function. Why? To save memory! Defining every property and method on the constructor function takes up a lot of memory, especially if you have many common properties and methods and a lot of created objects.
Defining them on a centralized, shared object which all the instances have access to, thus saves memory :D
Inside of a method, you can access the fields of the instance via this
and it works because of the following general rule:
When a function is called as a method of an object, its
this
is set to the object the method is called on.
function Car(name, logo) {
this.name = name;
this.logo = logo;
this.emptyFuel = false;
}
Car.prototype.addGas(quantity) {
if(this.emptyFuel) {
//...
}
// ...
}
const myCar = new Car('ferrari', 'horse');
myCar.addGas(10);
All right, now that we understood how the prototype chain works and how we simulate the creation of classes before ES6, let’s see how to use the new class
keyword introduced in 2015 to the language specification.
Class Syntax
Nowadays, JavaScript supports defining classes with the class
keyword. The new syntax is more similar to how classes are written in languages like Java and C#. This provides a lesser learning curve for new developers switching over from those languages.
In summary, this new syntax is basically a syntactic sugar over the existing prototype-based constructors that makes classes easier to read and write. However, what happens under the hood when you use the class
keyword is that you are actually using prototype-based constructors!
Class declaration
Let’s see what changes from the object constructor approach to the new class
syntax. Now, we can defined a class using the class
keyword followed by its name and the class body in curly brackets. The body will contain the definition of the properties and methods of the object.
class Car {
constructor(name, logo) {
this.name = name;
this.logo = logo;
this.emptyFuel = false;
}
addGas(quantity) {
//...
}
}
const myCar = new Car('ferrari', 'horse');
myCar.addGas(10);
Private Fields
By default, all instance fields are public in JavaScript. They can be directly accessed and assigned to. Since ES2022 you can define private fields for a class using #
sign prefixed with identifiers. This fields should never be accessed directly from outside the class.
To access private fields you will need to define getters and setters. With the keywords get
and set
you can define functions that will be executed when a property with the same name as the function is accessed or assigned to. Let’s see an example:
class Car {
#km;
constructor(name, logo) {
this.name = name;
this.logo = logo;
this.emptyFuel = false;
this.#km = 0;
}
get km() {
return this.#km;
}
set km(kilometers) {
this.#km = kilometers;
}
addGas(quantity) {
//...
}
}
const myCar = new Car('ferrari', 'horse');
myCar.km = 500;
Conclusion
Oh boy! That was a lot, I know. But, I hope that I was able to give an overview of how OOP works in JavaScript. I always like to quote the phrase from Seneca, a Roman philosopher, that said:
While we teach, we learn.
And this article was first to me, to really understand the details of the JavaScript language.
Hope you guys enjoyed. :D
Nice coding!