advanced
Step 16 of 20
Classes and OOP
JavaScript Programming
Classes and OOP
ES6 classes provide a clean, familiar syntax for object-oriented programming in JavaScript. Under the hood, classes are syntactic sugar over JavaScript's prototype-based inheritance, but they offer a much more readable and structured way to create objects and manage inheritance. Modern JavaScript classes support constructors, instance and static methods, getters and setters, private fields, and inheritance. If you have experience with classes in Python, Java, or C++, JavaScript classes will feel familiar.
Class Basics
class User {
// Private fields (ES2022)
#password;
constructor(name, email, password) {
this.name = name;
this.email = email;
this.#password = password;
}
// Instance method
greet() {
return `Hello, I'm ${this.name}`;
}
// Getter
get displayName() {
return `${this.name} <${this.email}>`;
}
// Setter
set fullName(value) {
const [first, last] = value.split(" ");
this.name = `${first} ${last}`;
}
// Private method
#hashPassword(password) {
return `hashed_${password}`;
}
// Static method
static fromJSON(json) {
const data = JSON.parse(json);
return new User(data.name, data.email, data.password);
}
// Static property
static MAX_NAME_LENGTH = 50;
}
const user = new User("Alice", "alice@example.com", "secret123");
console.log(user.greet()); // "Hello, I'm Alice"
console.log(user.displayName); // "Alice "
user.fullName = "Alice Johnson";
// console.log(user.#password); // SyntaxError — private
Inheritance
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
toString() {
return `[Animal: ${this.name}]`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
speak() {
return `${this.name} barks!`; // Override parent method
}
fetch(item) {
return `${this.name} fetches the ${item}`;
}
}
class Cat extends Animal {
speak() {
return `${this.name} meows!`;
}
}
const dog = new Dog("Buddy", "Lab");
const cat = new Cat("Whiskers");
console.log(dog.speak()); // "Buddy barks!"
console.log(cat.speak()); // "Whiskers meows!"
console.log(dog.fetch("ball")); // "Buddy fetches the ball"
// instanceof checks
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
Practical Class Example
class TodoList {
#items = [];
#nextId = 1;
add(title, priority = "medium") {
const item = {
id: this.#nextId++,
title,
priority,
done: false,
createdAt: new Date()
};
this.#items.push(item);
return item;
}
toggle(id) {
const item = this.#items.find(i => i.id === id);
if (!item) throw new Error(`Item ${id} not found`);
item.done = !item.done;
return item;
}
remove(id) {
const index = this.#items.findIndex(i => i.id === id);
if (index === -1) throw new Error(`Item ${id} not found`);
return this.#items.splice(index, 1)[0];
}
get pending() {
return this.#items.filter(i => !i.done);
}
get completed() {
return this.#items.filter(i => i.done);
}
get count() {
return this.#items.length;
}
toJSON() {
return JSON.stringify(this.#items, null, 2);
}
}
const todos = new TodoList();
todos.add("Learn JavaScript", "high");
todos.add("Build a project", "high");
todos.add("Write tests", "medium");
todos.toggle(1);
console.log(`Pending: ${todos.pending.length}`);
console.log(`Completed: ${todos.completed.length}`);
Mixins Pattern
// JavaScript only supports single inheritance
// Use mixins for multiple behavior composition
const Serializable = (Base) => class extends Base {
toJSON() {
return JSON.stringify(this);
}
static fromJSON(json) {
return Object.assign(new this(), JSON.parse(json));
}
};
const Validatable = (Base) => class extends Base {
validate() {
for (const [key, value] of Object.entries(this)) {
if (value === null || value === undefined) {
throw new Error(`${key} is required`);
}
}
return true;
}
};
class Product extends Serializable(Validatable(Object)) {
constructor(name, price) {
super();
this.name = name;
this.price = price;
}
}
const product = new Product("Laptop", 999);
product.validate();
console.log(product.toJSON());
Pro tip: Use private fields (#) for data that should not be accessed outside the class. Unlike the underscore convention (_private), # fields are truly private and enforced by the JavaScript engine. Use getters for computed or read-only properties and setters for validation on assignment.
Key Takeaways
- ES6 classes provide clean syntax for OOP with constructors, methods, getters, setters, and static members.
- Use
#prefix for truly private fields and methods (enforced by the engine, not just convention). - Inheritance uses
extendsandsuper()to call parent constructors and methods. - Use mixins (higher-order class functions) to compose multiple behaviors since JavaScript only supports single inheritance.
- Classes are syntactic sugar over prototypes — understanding prototypes helps when debugging.