First of all, a question arises: are there classes in JavaScript? Historically, JavaScript did not have classes. Technically, even now, there are no traditional classes as seen in other programming languages.
However, if you Google it, some articles will say, "Yes, JavaScript has classes," and that this feature was introduced in ECMAScript 2015 (also known as ES6). It’s important to note that JavaScript is primarily a prototype-based language, not an object-oriented or functional-based language. Everything in JavaScript is prototype-based.
Classes in JavaScript are primarily syntactic sugar over existing prototype-based inheritance mechanisms, meaning you don’t miss out on anything.
OOP in JavaScript
Object-Oriented Programming (OOP) is a programming paradigm, a structure or style of writing code (such as procedural, OOP, functional, etc.).
Objects in JavaScript
An object is a collection of properties (variables, constants, or key-value pairs) and methods (functions).
Why Use OOP?
Before OOP, code was becoming messy, and no chunk of the code could be reused. With the introduction of OOP in JavaScript, we can use features of other languages such as Java and C++. We are able to utilize features such as abstraction, encapsulation, inheritance, and polymorphism, which makes code more modular, reusable, and easier to maintain. OOP allows us to create classes and instances similar to Java and C++.
Parts of OOP
Object Literal
Constructor Function
Prototypes
Classes
Instances (
new
,this
)
The 4 Pillars of OOP
- Abstraction:
Abstraction is the concept of hiding complex implementation details and showing only the essential features of an object. This helps in reducing programming complexity and effort.
class Car {
constructor(model, year) {
this.model = model;
this.year = year;
}
start() {
console.log(`${this.model} is starting.`);
}
stop() {
console.log(`${this.model} is stopping.`);
}
}
const myCar = new Car('Toyota', 2020);
myCar.start(); // Output: Toyota is starting.
myCar.stop(); // Output: Toyota is stopping.
Here, the Car
class provides a simple interface to start and stop the car without showing the internal workings of how the car actually starts or stops.
- Encapsulation:
Encapsulation is the concept of wrapping data and methods that work on the data within a single unit, usually a class. This keeps data safe from outside interference and misuse.
class Person {
#name;
#age;
constructor(name, age) {
this.#name = name;
this.#age = age;
}
getDetails() {
return `Name: ${this.#name}, Age: ${this.#age}`;
}
}
const person = new Person('John', 30);
console.log(person.getDetails()); // Output: Name: John, Age: 30
// console.log(person.#name); // This will throw an error as #name is private
In this example, #name
and #age
are private fields, and they can only be accessed through the getDetails
method.
- Inheritance:
Inheritance is the mechanism by which one class (child class) can inherit the properties and methods of another class (parent class). This allows for code reuse and a hierarchical class structure.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
const myDog = new Dog('Rex');
myDog.speak(); // Output: Rex barks.
Here, the Dog
class inherits from the Animal
class and overrides the speak
method to provide a specific implementation.
- Polymorphism:
Polymorphism means "many shapes" and it allows objects of different classes to be treated as objects of a common superclass. It is achieved through method overriding and method overloading.
class Animal {
speak() {
console.log('Animal makes a sound.');
}
}
class Dog extends Animal {
speak() {
console.log('Dog barks.');
}
}
class Cat extends Animal {
speak() {
console.log('Cat meows.');
}
}
function makeAnimalSpeak(animal) {
animal.speak();
}
const dog = new Dog();
const cat = new Cat();
makeAnimalSpeak(dog); // Output: Dog barks.
makeAnimalSpeak(cat); // Output: Cat meows.
In this example, the makeAnimalSpeak
function accepts an object of type Animal
and calls its speak
method. This demonstrates polymorphism because both Dog
and Cat
are treated as Animal
objects and their respective speak
methods are called.
Object Literal
An object literal is simply an object defined with a pair of curly braces {}
. Here's an example:
const user = {
username: "harsh",
loginCount: 8,
signedIn: true,
getUserDetails: function(){
console.log("Got user details");
console.log(`username: ${this.username}`); // outputs: harsh
console.log(this); //it will print the current context
}
}
console.log(user.username); // outputs: harsh
console.log(this); // outputs: {}
console.log(user.getUserDetails());
//output of "console.log(user.getUserDetails());"
//Got user details from database
//username: harsh
//{
// username: 'harsh',
//loginCount: 8,
//signedIn: 'true',
//getUserDetails: [Function: getUserDetails]
//}
//undefined
Constructor Function
A constructor function is used to create multiple instances from a single object or object literal. When we use the new
keyword, an empty object is created, and the constructor function packs all the arguments inside this instance, injecting them with the this
keyword.
Example of Constructor Function:
function User(username, loginCount, isLoggedIn){
this.username = username;
this.loginCount = loginCount;
this.isLoggedIn = isLoggedIn;
return this; // It's optional to write this; it will implicitly return the values
}
const userOne = User("harsh", 12, true);
console.log(userOne);
// outputs: all the other values inside this, and in the end:
// username: 'harsh'
// loginCount: 12
// isLoggedIn: true
const userTwo = User("daksh", 18, true);
console.log(userOne); // values are overridden by userTwo, and data is changed
To create a new instance or copy that doesn't affect the original or other copies, we use the new
keyword:
const userOne = new User("harsh", 12, true);
const userTwo = new User("daksh", 18, false);
console.log(userOne);
console.log(userTwo);
//outputs:
//User { username: 'harsh', loginCount: 8, isLoggedIn: true }
//User { username: 'ChaiAurCode', loginCount: 12, isLoggedIn: false }
When we use the new
keyword, the following steps occur:
An empty object is created, known as an instance.
The constructor function is called, which packs all our arguments inside the instance.
The
this
keyword injects all the arguments inside the instance.The instance is returned.
Printing the Constructor
console.log(userOne.constructor);
// outputs: [Function: User]
Here, the constructor property is a reference to the function that created the instance.
By understanding these concepts, we can effectively use OOP in JavaScript to write more modular, reusable, and maintainable code.