OOP in JavaScript. How it works?

Kaleb Dalla
5 min readJun 28, 2024

Photo by James Harrison on Unsplash

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:

JavaScript code on Chrome’s console

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 classkeyword 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!

Sign up to discover human stories that deepen your understanding of the world.

Kaleb Dalla
Kaleb Dalla

Written by Kaleb Dalla

A Senior Software Engineering currently working at the biggest bank of Latin America - Itaú. A guy that loves learning and sharing knowledge.

No responses yet

Write a response