JavaScript has absolutely revolutionized modern web development. It’s everywhere nowadays – from browser-based applications to server-side development with Node.js, and even powering mobile apps through frameworks like React Native. But many developers still struggle with organizing their JavaScript code effectively, often relying on functional approaches that can lead to messy, hard-to-maintain codebases.
I’ve been there myself. For years, I wrote spaghetti code that worked but was a nightmare to update or expand. Then I discovered the power of JavaScript classes, which completely transformed how I structure my code. In this guide, I’ll show you everything you need to know about JavaScript classes to write cleaner, more maintainable code.
Before ES6 introduced the class keyword in 2015, writing object-oriented JavaScript was possible but awkward. Today, classes are an essential tool in every JavaScript developer’s toolkit. They provide:
Modern frameworks like React, Angular, and Vue heavily utilize class concepts, making them fundamental knowledge for any serious JavaScript developer.
The class keyword makes defining classes straightforward:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
// Creating an instance
const john = new Person("John", "Doe");
console.log(john.getFullName()); // Outputs: "John Doe"Code language: JavaScript (javascript) The constructor method is called automatically when you create a new instance with the new keyword. It’s where you initialize properties specific to each instance.
While the modern syntax is cleaner, understanding the legacy approach helps you work with older codebases and deepens your JavaScript knowledge:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// Methods are added to the prototype
Person.prototype.getFullName = function() {
return this.firstName + " " + this.lastName;
};
// Creating an instance
var john = new Person("John", "Doe");
console.log(john.getFullName()); // Outputs: "John Doe"Code language: JavaScript (javascript) In this approach, the constructor function serves dual purposes – defining the class and initializing instances. Methods are typically added to the prototype for memory efficiency.
Classes can have two types of properties:
Instance properties are unique to each object instance:
class Product {
constructor(name, price) {
this.name = name; // Instance property
this.price = price; // Instance property
this.timestamp = Date.now(); <em>// Automatically generated instance property</em>
}
}
const product1 = new Product("Laptop", 999);
const product2 = new Product("Phone", 699);
console.log(product1.name); // "Laptop"
console.log(product2.name); // "Phone"Code language: JavaScript (javascript) Static properties belong to the class itself, not individual instances:
class MathUtils {
static PI = 3.14159;
static MAX_VALUE = 1000;
static square(x) {
return x * x;
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.square(4)); // 16Code language: JavaScript (javascript) Static properties and methods are accessed directly on the class, not on instances.
One of the biggest limitations in early JavaScript classes was the lack of true privacy. Now, with modern JavaScript, we have private fields using the # prefix:
class BankAccount {
#balance = 0; // Private field
constructor(initialDeposit) {
if (initialDeposit > 0) {
this.#balance = initialDeposit;
}
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(100);
console.log(account.getBalance()); // 100
// console.log(account.#balance); // Error: Private fieldCode language: PHP (php) Private fields are only accessible within the class, enhancing encapsulation.
Getters and setters provide controlled access to properties:
class Employee {
constructor(firstName, lastName, birthDate) {
this.firstName = firstName;
this.lastName = lastName;
this._birthDate = new Date(birthDate);
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
get age() {
const today = new Date();
let age = today.getFullYear() - this._birthDate.getFullYear();
const monthDiff = today.getMonth() - this._birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < this._birthDate.getDate())) {
age--;
}
return age;
}
set birthDate(date) {
if (date > new Date()) {
throw new Error("Birth date cannot be in the future");
}
this._birthDate = new Date(date);
}
}
const emp = new Employee("Jane", "Smith", "1990-05-15");
console.log(emp.fullName); // "Jane Smith"
console.log(emp.age); // Calculates age dynamically
emp.birthDate = "1995-10-20"; // Uses the setterCode language: JavaScript (javascript) Getters are accessed like properties but execute a method, allowing for computed properties. Setters enable validation before setting values.
Inheritance lets you create specialized classes based on more general ones:
class Vehicle {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
getInfo() {
return `${this.year} ${this.make} ${this.model}`;
}
startEngine() {
return "Engine started";
}
}
class Car extends Vehicle {
constructor(make, model, year, doors) {
super(make, model, year); // Call parent constructor
this.doors = doors;
}
// Override parent method
startEngine() {
return `${super.startEngine()} with push button`;
}
// Add new method
honk() {
return "Beep beep!";
}
}
const myCar = new Car("Honda", "Accord", 2023, 4);
console.log(myCar.getInfo()); // "2023 Honda Accord"
console.log(myCar.startEngine()); // "Engine started with push button"
console.log(myCar.honk()); // "Beep beep!"Code language: JavaScript (javascript) The extends keyword establishes inheritance, and super calls the parent class’s constructor or methods. Learn about Javascript Inherience more in depth.
While inheritance is powerful, composition often provides more flexibility:
class Engine {
start() {
return "Engine running";
}
stop() {
return "Engine stopped";
}
}
class Radio {
turnOn() {
return "Radio playing";
}
turnOff() {
return "Radio off";
}
}
class Car {
constructor() {
this.engine = new Engine();
this.radio = new Radio();
}
startCar() {
return this.engine.start();
}
playMusic() {
return this.radio.turnOn();
}
}
const myCar = new Car();
console.log(myCar.startCar()); // "Engine running"
console.log(myCar.playMusic()); // "Radio playing"Code language: JavaScript (javascript) Instead of inheriting functionality, composition embeds objects within other objects, creating more flexible relationships.
To get the most from JavaScript classes:
Let’s put everything together with a practical example:
class Product {
constructor(id, name, price) {
this.id = id;
this.name = name;
this.price = price;
}
}
class CartItem {
constructor(product, quantity = 1) {
this.product = product;
this.quantity = quantity;
}
get subtotal() {
return this.product.price * this.quantity;
}
}
class ShoppingCart {
#items = [];
addItem(product, quantity = 1) {
const existingItem = this.#items.find(item => item.product.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
this.#items.push(new CartItem(product, quantity));
}
}
removeItem(productId) {
const index = this.#items.findIndex(item => item.product.id === productId);
if (index !== -1) {
this.#items.splice(index, 1);
return true;
}
return false;
}
updateQuantity(productId, quantity) {
if (quantity <= 0) {
return this.removeItem(productId);
}
const item = this.#items.find(item => item.product.id === productId);
if (item) {
item.quantity = quantity;
return true;
}
return false;
}
getItems() {
return [...this.#items]; // Return a copy to prevent direct manipulation
}
get total() {
return this.#items.reduce((sum, item) => sum + item.subtotal, 0);
}
clear() {
this.#items = [];
}
}
// Usage example
const laptop = new Product(1, "Laptop", 999);
const phone = new Product(2, "Smartphone", 699);
const cart = new ShoppingCart();
cart.addItem(laptop);
cart.addItem(phone, 2);
console.log(cart.getItems());
console.log(`Total: $${cart.total}`); // Total: $2397Code language: PHP (php) This shopping cart example demonstrates class composition, private fields, getters, and business logic encapsulation.
JavaScript classes give you powerful tools to organize your code better. Whether you’re working with React components, Node.js services, or vanilla JavaScript, understanding classes will make you a more effective developer. Learn more on MDN Documentations.
Remember, classes aren’t always the right solution – JavaScript’s functional approaches are still valuable. But having classes in your toolkit gives you more options for structuring maintainable code.
Happy coding!
Learn python file handling from scratch! This comprehensive guide walks you through reading, writing, and managing files in Python with real-world examples, troubleshooting tips, and…
You've conquered the service worker lifecycle, mastered caching strategies, and explored advanced features. Now it's time to lock down your implementation with battle-tested service worker…
Unlock the full potential of service workers with advanced features like push notifications, background sync, and performance optimization techniques that transform your web app into…
This website uses cookies.
View Comments
Ali, I have gone through your post and it really helped me alot. Still i have some queries, Can you please tell me about overriding Native Object Methods. Looking for your quick response.
As you may already know from my comment on this post, javascript isn't built in traditional object oriented way. Same way, overriding doesn't exist by default here. But you can simulate it by assigning the function to your own definition. Such as "JSON.stringify = function(){ return 'whatever';};" will change the native 'stringify' method. Check this link: http://jsbin.com/moratunece/2/edit?js,output . Hope this helps!