Module 20: Classes and OOP
Object-Oriented Programming (OOP) in JavaScript uses classes to create reusable, organized code. ES6 classes provide a cleaner syntax for creating objects and handling inheritance.
1. Understanding Classes
1.1 What are Classes?
Classes are blueprints for creating objects with predefined properties and methods.
// Before ES6 (Constructor Functions)
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hello, I'm ${this.name}`);
};
// ES6 Class Syntax
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
}
// Usage
const john = new Person('John', 30);
john.greet(); // "Hello, I'm John"
Classes are Syntactic Sugar
ES6 classes are syntactic sugar over JavaScript's prototype-based inheritance. Under the hood, they still use prototypes.
2. Class Syntax
2.1 Class Declaration
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
perimeter() {
return 2 * (this.width + this.height);
}
}
const rect = new Rectangle(10, 5);
console.log(rect.area()); // 50
console.log(rect.perimeter()); // 30
2.2 Class Expression
// Anonymous class expression
const Rectangle = class {
constructor(width, height) {
this.width = width;
this.height = height;
}
};
// Named class expression
const Rectangle = class Rect {
constructor(width, height) {
this.width = width;
this.height = height;
}
getClassName() {
return Rect.name; // "Rect"
}
};
3. Constructor
3.1 Basic Constructor
class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.createdAt = new Date();
}
}
const user = new User('John', 'john@example.com');
console.log(user.name); // "John"
console.log(user.createdAt); // Current date
3.2 Constructor Validation
class User {
constructor(name, email) {
if (!name || typeof name !== 'string') {
throw new Error('Name must be a string');
}
if (!email || !email.includes('@')) {
throw new Error('Invalid email');
}
this.name = name;
this.email = email;
}
}
try {
const user = new User('', 'invalid');
} catch (error) {
console.error(error.message);
}
3.3 Default Parameters
class User {
constructor(name, role = 'user', active = true) {
this.name = name;
this.role = role;
this.active = active;
}
}
const user1 = new User('John');
console.log(user1.role); // "user"
const user2 = new User('Admin', 'admin');
console.log(user2.role); // "admin"
4. Methods
4.1 Instance Methods
class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
multiply(a, b) {
return a * b;
}
divide(a, b) {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
}
const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
console.log(calc.multiply(4, 2)); // 8
4.2 Method Chaining
class QueryBuilder {
constructor() {
this.query = '';
}
select(fields) {
this.query += `SELECT ${fields} `;
return this; // Return this for chaining
}
from(table) {
this.query += `FROM ${table} `;
return this;
}
where(condition) {
this.query += `WHERE ${condition} `;
return this;
}
build() {
return this.query.trim();
}
}
const query = new QueryBuilder()
.select('*')
.from('users')
.where('age > 18')
.build();
console.log(query); // "SELECT * FROM users WHERE age > 18"
5. Static Members
5.1 Static Methods
class MathUtils {
static PI = 3.14159;
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
static max(...numbers) {
return Math.max(...numbers);
}
}
// Called on class, not instance
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.max(1, 5, 3)); // 5
// Not available on instances
const util = new MathUtils();
// util.add(5, 3); // ❌ Error
5.2 Static Properties
class User {
static totalUsers = 0;
constructor(name) {
this.name = name;
User.totalUsers++;
}
static getUserCount() {
return User.totalUsers;
}
}
new User('John');
new User('Jane');
new User('Bob');
console.log(User.getUserCount()); // 3
6. Getters and Setters
6.1 Basic Getters and Setters
class Circle {
constructor(radius) {
this._radius = radius;
}
get radius() {
return this._radius;
}
set radius(value) {
if (value <= 0) {
throw new Error('Radius must be positive');
}
this._radius = value;
}
get area() {
return Math.PI * this._radius ** 2;
}
get diameter() {
return this._radius * 2;
}
}
const circle = new Circle(5);
console.log(circle.radius); // 5
console.log(circle.area); // 78.54
console.log(circle.diameter); // 10
circle.radius = 10; // Uses setter
console.log(circle.area); // 314.16
6.2 Computed Properties
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
set fullName(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
}
}
const person = new Person('John', 'Doe');
console.log(person.fullName); // "John Doe"
person.fullName = 'Jane Smith';
console.log(person.firstName); // "Jane"
console.log(person.lastName); // "Smith"
7. Inheritance
7.1 extends Keyword
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
speak() {
console.log(`${this.name} barks`);
}
fetch() {
console.log(`${this.name} fetches the ball`);
}
}
const dog = new Dog('Rex', 'Labrador');
dog.speak(); // "Rex barks"
dog.fetch(); // "Rex fetches the ball"
7.2 super Keyword
class Shape {
constructor(color) {
this.color = color;
}
describe() {
return `A ${this.color} shape`;
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color); // Call parent constructor
this.radius = radius;
}
describe() {
// Call parent method
return `${super.describe()} with radius ${this.radius}`;
}
area() {
return Math.PI * this.radius ** 2;
}
}
const circle = new Circle('red', 5);
console.log(circle.describe()); // "A red shape with radius 5"
console.log(circle.area()); // 78.54
7.3 Method Overriding
class Vehicle {
constructor(brand) {
this.brand = brand;
}
start() {
console.log(`${this.brand} vehicle starting...`);
}
stop() {
console.log(`${this.brand} vehicle stopping...`);
}
}
class ElectricCar extends Vehicle {
constructor(brand, batteryCapacity) {
super(brand);
this.batteryCapacity = batteryCapacity;
}
start() {
console.log(`${this.brand} electric car starting silently...`);
console.log(`Battery: ${this.batteryCapacity}%`);
}
charge() {
console.log('Charging battery...');
}
}
const tesla = new ElectricCar('Tesla', 85);
tesla.start(); // Overridden method
tesla.charge(); // New method
tesla.stop(); // Inherited method
8. Private Fields
8.1 Private Properties
class BankAccount {
#balance = 0; // Private field
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
return true;
}
return false;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
console.log(account.getBalance()); // 1000
account.deposit(500);
console.log(account.getBalance()); // 1500
// console.log(account.#balance); // ❌ SyntaxError: Private field
8.2 Private Methods
class User {
#password;
constructor(username, password) {
this.username = username;
this.#password = this.#hashPassword(password);
}
#hashPassword(password) {
// Private method
return btoa(password); // Simple encoding for demo
}
checkPassword(password) {
return this.#password === this.#hashPassword(password);
}
}
const user = new User('john', 'secret123');
console.log(user.checkPassword('secret123')); // true
// user.#hashPassword('test'); // ❌ Error: Private method
9. OOP Principles
9.1 Encapsulation
class Counter {
#count = 0;
increment() {
this.#count++;
}
decrement() {
this.#count--;
}
getCount() {
return this.#count;
}
reset() {
this.#count = 0;
}
}
const counter = new Counter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
9.2 Abstraction
class Database {
#connection;
constructor(config) {
this.#connection = this.#connect(config);
}
#connect(config) {
// Complex connection logic hidden
return { connected: true, config };
}
#executeQuery(query) {
// Complex query execution hidden
return { result: 'data' };
}
// Simple public interface
getUser(id) {
return this.#executeQuery(`SELECT * FROM users WHERE id = ${id}`);
}
createUser(userData) {
return this.#executeQuery(`INSERT INTO users ...`);
}
}
9.3 Polymorphism
class Shape {
area() {
throw new Error('area() must be implemented');
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
}
function printArea(shape) {
console.log(`Area: ${shape.area()}`);
}
printArea(new Rectangle(5, 10)); // Area: 50
printArea(new Circle(5)); // Area: 78.54
10. Real-World Examples
10.1 User Management System
class User {
static #users = [];
constructor(username, email, role = 'user') {
this.id = Date.now();
this.username = username;
this.email = email;
this.role = role;
this.createdAt = new Date();
User.#users.push(this);
}
hasPermission(action) {
const permissions = {
user: ['read'],
admin: ['read', 'write', 'delete']
};
return permissions[this.role]?.includes(action) ?? false;
}
static findByEmail(email) {
return User.#users.find(u => u.email === email);
}
static getAllUsers() {
return User.#users;
}
}
class Admin extends User {
constructor(username, email) {
super(username, email, 'admin');
}
deleteUser(userId) {
console.log(`Admin ${this.username} deleting user ${userId}`);
}
}
const user = new User('john', 'john@example.com');
const admin = new Admin('admin', 'admin@example.com');
console.log(user.hasPermission('delete')); // false
console.log(admin.hasPermission('delete')); // true
10.2 Shopping Cart
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 total() {
return this.product.price * this.quantity;
}
}
class ShoppingCart {
#items = [];
addItem(product, quantity = 1) {
const existing = this.#items.find(item =>
item.product.id === product.id
);
if (existing) {
existing.quantity += quantity;
} else {
this.#items.push(new CartItem(product, quantity));
}
}
removeItem(productId) {
this.#items = this.#items.filter(item =>
item.product.id !== productId
);
}
get total() {
return this.#items.reduce((sum, item) => sum + item.total, 0);
}
get itemCount() {
return this.#items.reduce((sum, item) => sum + item.quantity, 0);
}
getItems() {
return [...this.#items];
}
clear() {
this.#items = [];
}
}
// Usage
const cart = new ShoppingCart();
const laptop = new Product(1, 'Laptop', 999);
const mouse = new Product(2, 'Mouse', 29);
cart.addItem(laptop, 1);
cart.addItem(mouse, 2);
console.log('Total:', cart.total); // 1057
console.log('Items:', cart.itemCount); // 3
Summary
In this module, you learned:
- ✅ ES6 class syntax and constructor functions
- ✅ Instance methods, static methods, and properties
- ✅ Getters and setters for computed properties
- ✅ Inheritance with extends and super
- ✅ Private fields and methods
- ✅ OOP principles: encapsulation, abstraction, polymorphism
- ✅ Real-world examples and patterns
Next Steps
In Module 21, you'll learn about Prototypes and Inheritance, understanding the mechanism behind JavaScript's class system.
Practice Exercises
- Create a Vehicle hierarchy with Car, Truck, and Motorcycle classes
- Build a Task Manager with Task and TaskList classes
- Implement a BankAccount system with different account types
- Create a Game with Player, Enemy, and Item classes
- Build a Library system with Book, Member, and Library classes
- Implement a validation framework using classes
- Create an event emitter system
- Build a plugin system using inheritance