
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.
Why Learn about JavaScript Classes?
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:
- Better code organization – Group related functionality together
- Improved maintainability – Make your code easier to update and debug
- Enhanced reusability – Create components you can use throughout your application
- Clearer mental model – Structure your code in a way that’s intuitive for most developers
Modern frameworks like React, Angular, and Vue heavily utilize class concepts, making them fundamental knowledge for any serious JavaScript developer.
Creating Your First JavaScript Class
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.
Understanding the Legacy Approach (Pre-ES6)
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.
Properties in JavaScript Classes
Classes can have two types of properties:
Instance 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
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)); // 16
Code language: JavaScript (javascript)
Static properties and methods are accessed directly on the class, not on instances.
Private Fields and Methods
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 field
Code language: PHP (php)
Private fields are only accessible within the class, enhancing encapsulation.
Getters and Setters
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 setter
Code language: JavaScript (javascript)
Getters are accessed like properties but execute a method, allowing for computed properties. Setters enable validation before setting values.
Inheritance with JavaScript Classes
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.
Using Composition with Classes
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.
Best Practices for Using JavaScript Classes
To get the most from JavaScript classes:
- Keep classes focused – Each class should have a single responsibility
- Use descriptive naming – Class names should be nouns, method names should be verbs
- Consider composition over inheritance – Inheritance creates tight coupling; composition is often more flexible
- Keep inheritance hierarchies shallow – Deep inheritance chains become hard to understand
- Document your classes – Use JSDoc comments to document parameters, return values, and class purpose
- Test your classes – Write unit tests to verify class behavior
Real-World Example: Building a Shopping Cart
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: $2397
Code language: PHP (php)
This shopping cart example demonstrates class composition, private fields, getters, and business logic encapsulation.
Conclusion
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!
Discover more from CodeSamplez.com
Subscribe to get the latest posts sent to your email.
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!