Skip to main content

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

  1. Create a Vehicle hierarchy with Car, Truck, and Motorcycle classes
  2. Build a Task Manager with Task and TaskList classes
  3. Implement a BankAccount system with different account types
  4. Create a Game with Player, Enemy, and Item classes
  5. Build a Library system with Book, Member, and Library classes
  6. Implement a validation framework using classes
  7. Create an event emitter system
  8. Build a plugin system using inheritance

Additional Resources