๐ Day 24 — Classes & Object-Oriented JavaScript
Hey Guys Object-Oriented Programming (OOP) helps structure complex applications. Today we’ll explore ES6 classes, inheritance, prototypes, encapsulation, and practical patterns for building maintainable JavaScript code.
๐ Why OOP in JavaScript?
JavaScript is prototype-based, but ES6 `class` syntax gives a clearer, more familiar structure for building objects. OOP helps group data and behavior, enforce invariants, and create reusable components.
๐ค Basic class syntax
A class encapsulates state (properties) and behavior (methods). The `constructor` is called when creating an instance with `new`.
class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.created = Date.now();
}
greet() {
return `Hello, ${this.name}`;
}
toJSON() {
return { name: this.name, email: this.email };
}
}
const u = new User('Rahul', 'rahul@example.com');
console.log(u.greet());
๐งฌ Inheritance & extends
Use `extends` to build specialized classes. Always call `super()` in the subclass constructor to initialize the base class.
class Admin extends User {
constructor(name, email, level = 1) {
super(name, email);
this.level = level;
}
canDelete() {
return this.level > 0;
}
}
const a = new Admin('Manager', 'mgr@example.com', 2);
console.log(a.canDelete()); // true
๐ Prototypes under the hood
Classes are syntactic sugar over prototype-based inheritance. `User.prototype` contains methods shared by instances.
console.log(Object.getPrototypeOf(u) === User.prototype); // true
๐ Encapsulation: private fields & methods
Modern JS supports private fields (`#`) for encapsulation and `get`/`set` for controlled access.
class BankAccount {
#balance = 0;
constructor(initial = 0) {
this.#balance = initial;
}
deposit(amount) {
if (amount <= 0) throw new Error('Amount must be positive');
this.#balance += amount;
}
get balance() { return this.#balance; }
}
⚙ Composition over inheritance
Prefer composition when behavior can be assembled from smaller parts. It often results in more flexible and testable code than deep inheritance hierarchies.
function withLogging(obj) {
return new Proxy(obj, {
get(target, prop, receiver) {
const v = Reflect.get(target, prop, receiver);
if (typeof v === 'function') {
return function(...args) {
console.log(`Calling ${String(prop)}`, args);
return v.apply(this, args);
};
}
return v;
}
});
}
๐งช Patterns: Factory, Singleton, Module
- Factory: function that returns configured objects. - Singleton: single shared instance (use sparingly). - Module: encapsulates state and exports behavior (ES modules).
๐ Practical example: Component class
Basic UI component pattern using classes — each instance manages its DOM root and lifecycle.
class Counter {
constructor(root) {
this.root = root;
this.count = 0;
this.render();
}
increment() {
this.count++;
this.update();
}
render() {
this.root.innerHTML = `
${this.count}
`;
this.root.querySelector('.inc').addEventListener('click', () => this.increment());
}
update() {
this.root.querySelector('.value').textContent = this.count;
}
}
๐ Best practices
- Favor small classes focused on a single responsibility.
- Use composition to add behavior dynamically.
- Keep state immutable where possible — makes reasoning easier.
- Write unit tests for class behavior (constructor, methods).
๐งพ Migration tips from prototypes to classes
- Map prototype functions to class methods.
- Move shared behavior onto prototypes (or class methods) rather than on instances.
- Test before refactor, then swap to `class` syntax for readability.
๐ Practice tasks (real-world)
- Build a `Modal` class that controls open/close and focus trapping.
- Create a `Store` class for app state with subscribe/publish methods.
- Refactor an existing function-based module into a class-based module and write tests.
๐ฏ Summary
ES6 classes give you a clean, familiar syntax for building structured JavaScript. Understand the prototype roots, use private fields for encapsulation, prefer composition when possible, and keep classes single-responsibility for maintainable code.
Comments
Post a Comment