Mastering Object-Oriented Programming (OOP) in JavaScript
Object-Oriented Programming (OOP) is an essential concept in JavaScript. It enables developers to write modular, reusable, and maintainable code by organizing it around data (objects), rather than functions and logic. This article explores key OOP concepts in JavaScript covering classes, objects, inheritance, encapsulation, and polymorphism. Additionally, it offers practical examples for each concept.
Prerequisites
Basic JavaScript syntax and concepts, such as variables, data types, control structures, and basic operators.
Understanding of functions and scope.
Understanding of prototypes.
Knowledge of ES6+ features essential for writing clean and efficient code.
Key Concepts of OOP in JavaScript
Classes and Objects
Encapsulation
Inheritance
Polymorphism
Classes and Objects
In JavaScript, classes are blueprints for creating objects. Objects are instances of classes that can have properties and methods.
Creating a Class
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const person1 = new Person("John", 30);
person1.greet(); // Output: Hello, my name is John and I am 30 years old.
Explanation:
class Person {...}
defines a new class namedPerson
.constructor(name, age) {...}
is a special method for creating and initializing an object created with thePerson
class.this.name = name;
assigns thename
parameter to thename
property of the instance.this.age = age;
assigns theage
parameter to theage
property of the instance.greet() {...}
is a method within thePerson
class. It logs a greeting message to the console using template literals to embed thename
andage
properties of the instance.const person1 = new Person("John", 30);
creates a new instance of thePerson
class with the name "John" and age 30.person1.greet();
calls thegreet()
method on theperson1
instance.
Encapsulation
Encapsulation bundles data (properties) and methods (functions) into a class. It protects object states and controls data access. This approach also makes code more modular and flexible.
Using Encapsulation
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
get name() {
return this._name;
}
set name(newName) {
if (newName.length > 0) {
this._name = newName;
} else {
console.log("Name cannot be empty");
}
}
greet() {
console.log(`Hello, my name is ${this._name} and I am ${this._age} years old.`)
}
}
const person2 = new Person("Jane", 20);
console.log(person2.name); // Output: Jane
person2.name = "Doe";
console.log(person2.name); // Output: Doe
Explanation
The properties
_name
and_age
are encapsulated within the class.The use of getter and setter methods (
get name()
andset name(newName)
) allows controlled access to these properties.The underscore (_) prefix indicates that these properties are intended to be private.
This helps in protecting the internal state of the object and ensures that any changes to the properties are validated.
The
get name()
method allows reading the_name
property.return this._name;
returns the value of the_name
property.The
set name(newName)
method allows modifying the_name
property with validation.console.log(person2.name);
accesses thename
property using the getter method.person2.name = "Doe";
uses the setter method to update the_name
property to "Doe".
Inheritance
Inheritance creates a new class from an existing one. The new class, called a subclass, inherits properties and methods. The existing class is the superclass or base class. It's key for building code that's modular, reusable, and easy to maintain. Additionally, it allows you to expand on existing code, create hierarchies, and write flexible, abstract code.
Implementing Inheritance
class Vehicle {
constructor(make, model) {
this.make = make;
this.model = model;
}
start() {
console.log(`${this.make} ${this.model} is starting.`);
}
stop() {
console.log(`${this.make} ${this.model} is stopping`)
}
}
class Car extends Vehicle {
constructor(make, model, doors) {
super(make, model); // Call the constructor of the base class
this.doors = doors;
}
honk() {
console.log(`${this.make} ${this.model} is honking`)
}
}
class Bike extends Vehicle {
constructor(make, model, type) {
super(make, model);
this.type = type;
}
ringBell() {
console.log(`${this.make} ${this.model} is ringing the bell`)
}
}
const myCar = new Car("Toyota", "Corolla", 4);
myCar.start(); // Output: Toyota Corolla is starting
myCar.honk(); // Output: Toyota Corolla is honking.
myCar.stop(); // Output: Toyota Corolla is stopping.
const myBike = new Bike("Giant", "Escape", "Road");
myBike.start(); // Output: Giant Escape is starting.
myBike.ringBell(); // Output: Giant Escape is ringing the bell.
myBike.stop(); // Output: Giant Escape is stopping.
Explanation:
class Vehicle { ... }
creates a new class namedVehicle
.this.make = make;
assigns themake
parameter to themake
property of the instance.this.model = model;
assigns themodel
parameter to themodel
property of the instance.start() { ... }
is a method that logs a message indicating the vehicle is starting.stop() { ... }
is a method that logs a message indicating the vehicle is stopping.class Car extends Vehicle { ... }
defines a new class namedCar
that extends theVehicle
class.constructor(make, model, doors) { ... }
is a special method for creating and initializing an object created with theCar
class.super(make, model);
calls the constructor of the base classVehicle
to initialize themake
andmodel
properties.this.doors = doors;
assigns thedoors
parameter to thedoors
property of the instance.honk() { ... }
is a method that logs a message indicating the car is honking.class Bike extends Vehicle { ... }
defines a new classBike
that extends theVehicle
class.constructor(make, model, type) { ... }
is a special method. It creates and sets up an object made with theBike
class.super(make, model);
calls the constructor of the base classVehicle
to initialize themake
andmodel
properties.this.type = type;
assigns thetype
parameter to thetype
property of the instance.ringBell() { ... }
is a method that logs a message indicating the bike is ringing the bell.const myCar = new Car("Toyota", "Corolla", 4);
creates a new instance of theCar
class with themake
"Toyota",model
"Corolla", anddoors
4.const myBike = new Bike("Giant", "Escape", "Road");
creates a new instance of theBike
class with the make "Giant", model "Escape", and type "Road".myCar.start();
calls thestart
method on themyCar
instance, logging "Toyota Corolla is starting."myCar.honk();
calls thehonk
method on themyCar
instance, logging "Toyota Corolla is honking."myBike.stop();
calls thestop
method on themyBike
instance, logging "Giant Escape is stopping."myBike.ringBell();
calls theringBell
method on themyBike
instance, logging "Giant Escape is ringing the bell."
Polymorphism
Polymorphism is a concept that allows using a function in various ways. It means a single function can have different forms and behaviors. In JavaScript, developers achieve this by overriding methods and implementing interfaces.
i. Method Overriding
Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its superclass.
Example: Method Overriding
class Animal {
speak() {
console.log("This animal makes a sound.");
}
}
class Dog extends Animal {
speak() {
console.log("The dog barks.");
}
}
class Cat extends Animal {
speak() {
console.log("The cat meows.");
}
}
// Creating instances of Dog and Cat
const dog = new Dog();
const cat = new Cat();
// Calling the speak method on each instance
dog.speak(); // Output: The dog barks.
cat.speak(); // Output: The cat meows.
Explanation:
The
Animal
class has a methodspeak
.The
Dog
andCat
classes override thespeak
method to provide specific implementations.When
speak
is called on instances ofDog
andCat
, execute the overridden methods in their respective subclasses.
ii. Interface Implementation
Unlike some OOP languages, JavaScript doesn't have interfaces. However, you can mimic this. Define a common method structure that different classes can use.
Example: Interface-like Behavior
class Bird {
fly() {
console.log("This bird is flying.");
}
}
class Sparrow extends Bird {
fly() {
console.log("The sparrow flies swiftly.");
}
}
class Eagle extends Bird {
fly() {
console.log("The eagle soars high.");
}
}
// Array of different birds
const birds = [new Sparrow(), new Eagle()];
// Calling the fly method on each bird
birds.forEach(bird => {
bird.fly();
});
// Output:
// The sparrow flies swiftly.
// The eagle soars high.
Explanation:
The
Bird
class has a methodfly
.The
Sparrow
andEagle
classes implement their own versions of thefly
method.By treating all objects as instances of
Bird
, we can callfly
on each and get the correct implementation.
Conclusion
Mastering object-oriented programming in JavaScript enhances code organization and robustness. By leveraging classes, encapsulation, inheritance, and polymorphism, developers can create more scalable and maintainable applications. Dive deeper into these concepts and elevate your coding skills to the next level. Explore the resources below and start building more powerful JavaScript applications today!
For more in-depth information and examples, consider exploring the following resources:
Subscribe to my newsletter
Read articles from bernard bebeni directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by