Understanding Classes and Object-Oriented Programming (OOP) in JavaScript
Sonu Tiwari
Crafting Stunning UI/UX for a Billion Users Across Demographics | Let’s Connect!
Object-Oriented Programming (OOP) is a way to organize code into reusable “blueprints” called classes. This helps you structure and manage code in a way that’s scalable, modular, and easy to maintain. With ES6, JavaScript introduced class syntax, making it easier to work with OOP concepts in JavaScript. In this article, we’ll break down key OOP concepts using ES6 classes, including inheritance, super(), this, static methods, and private fields.
1. Classes and Constructor Functions
Before ES6, JavaScript didn’t have a built-in class syntax like other languages (e.g., Java or Python). Developers used constructor functions to define and create objects. Here’s a quick example:
// Constructor function
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
const dog = new Animal("Dog");
dog.speak(); // Output: Dog makes a noise.
With ES6, you can achieve the same thing more elegantly with class syntax:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
const dog = new Animal("Dog");
dog.speak(); // Output: Dog makes a noise.
As you can see, ES6 classes offer a cleaner, more readable syntax while providing the same functionality as constructor functions. The constructor method inside the class is a special method that initializes the object’s properties when created.
2. Understanding super() and Inheritance in Classes
Inheritance is an OOP feature that allows one class to inherit properties and methods from another. For example, if we create a Dog class that extends Animal, the Dog class will automatically inherit all methods and properties of the Animal class. We use the extends keyword to achieve inheritance.
class Dog extends Animal {
constructor(name, breed) {
super(name); // Calls the parent class constructor
this.breed = breed;
}
speak() {
console.log(`${this.name} the ${this.breed} barks!`);
}
}
const myDog = new Dog("Buddy", "Golden Retriever");
myDog.speak(); // Output: Buddy the Golden Retriever barks!
In this example:
3. How this Works within Class Methods
The keyword this in JavaScript can sometimes be confusing. Within class methods, this refers to the instance of the class itself, allowing access to the instance’s properties and other methods.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
introduce() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
haveBirthday() {
this.age += 1; // Accessing the instance's properties using 'this'
console.log(`${this.name} just turned ${this.age}!`);
}
}
const person = new Person("Alice", 30);
person.introduce(); // Output: Hello, my name is Alice and I am 30 years old.
person.haveBirthday(); // Output: Alice just turned 31!
In the example above, this.name and this.age refer to the instance properties of the Person object, allowing the methods to access and modify them. It’s important to note that this within an arrow function doesn’t behave the same way, as arrow functions don’t have their own this context.
4. Static Methods
Static methods are functions defined in a class that are not accessible to individual instances. They are called directly on the class itself. Static methods are useful for utility functions related to the class but don’t need access to instance-specific properties.
class Calculator {
static add(a, b) {
return a + b;
}
static subtract(a, b) {
return a - b;
}
}
console.log(Calculator.add(5, 3)); // Output: 8
console.log(Calculator.subtract(10, 4)); // Output: 6
In this example:
5. Private Fields
In ES2020, JavaScript introduced private fields, a way to create truly private properties within classes. Private fields are denoted by a # prefix and can only be accessed within the class they’re defined in. Attempting to access them outside the class results in an error.
class BankAccount {
#balance = 0; // Private field
deposit(amount) {
this.#balance += amount;
console.log(`Deposited: $${amount}. New balance: $${this.#balance}`);
}
withdraw(amount) {
if (amount > this.#balance) {
console.log("Insufficient funds.");
} else {
this.#balance -= amount;
console.log(`Withdrew: $${amount}. Remaining balance: $${this.#balance}`);
}
}
}
const account = new BankAccount();
account.deposit(100); // Deposited: $100. New balance: $100
account.withdraw(50); // Withdrew: $50. Remaining balance: $50
// account.#balance = 1000; // Error: Private field '#balance' must be declared in an enclosing class
Here:
Practical Use Case: A Library System
Let’s tie these concepts together with a practical example of a basic library system. We’ll create classes for Library, Book, and Member, using inheritance, static methods, and private fields.
class Book {
constructor(title, author) {
this.title = title;
this.author = author;
}
getDetails() {
return ${this.title} by ${this.author};
}
}
class Member {
#borrowedBooks = []; // Private field
borrowBook(book) {
this.#borrowedBooks.push(book);
console.log(`Borrowed: ${book.getDetails()}`);
}
returnBook(book) {
const index = this.#borrowedBooks.indexOf(book);
if (index !== -1) {
this.#borrowedBooks.splice(index, 1);
console.log(`Returned: ${book.getDetails()}`);
}
}
static maxBooksAllowed() {
return 5; // Static method
}
}
class Library {
#books = []; // Private field for books in the library
addBook(book) {
this.#books.push(book);
console.log(`Added: ${book.getDetails()} to library.`);
}
listBooks() {
console.log("Books available in the library:");
this.#books.forEach(book => console.log(book.getDetails()));
}
}
// Example usage
const library = new Library();
const book1 = new Book("The Great Gatsby", "F. Scott Fitzgerald");
const book2 = new Book("1984", "George Orwell");
library.addBook(book1);
library.addBook(book2);
library.listBooks();
const member = new Member();
member.borrowBook(book1);
member.returnBook(book1);
console.log(`Max books allowed to borrow: ${Member.maxBooksAllowed()}`);
In this example:
Conclusion
JavaScript’s ES6 class syntax provides a clear, powerful way to implement OOP concepts. By using classes, inheritance, this, super(), static methods, and private fields, you can create well-organized, modular, and maintainable code structures. This foundational knowledge equips you to build more complex applications with reusable components.