Object-Oriented Programming (OOP) is a programming paradigm that organizes code around objects and classes. JavaScript's class syntax (introduced in ES6) provides a clean, familiar way to create objects, define behavior, and establish relationships through inheritance. Classes encapsulate data and functionality, making code more organized, reusable, and maintainable. Whether building simple applications or complex systems, understanding OOP and classes is essential for modern JavaScript development. Let's master classes and OOP!
What is Object-Oriented Programming?
OOP Core Principles:
- Encapsulation: Bundle data and methods together
- Abstraction: Hide complex implementation details
- Inheritance: Share code between related classes
- Polymorphism: Objects of different types can be used interchangeably
Classes provide a structured way to create objects with shared properties and methods.
Creating a Basic Class
// Define a class
class Person {
// Constructor - runs when creating new instance
constructor(name, age) {
this.name = name; // Instance property
this.age = age;
}
// Method (shared by all instances)
greet() {
console.log(`Hello, I'm ${this.name} and I'm ${this.age} years old.`);
}
// Another method
celebrateBirthday() {
this.age++;
console.log(`Happy birthday! Now I'm ${this.age}.`);
}
}
// Create instances (objects) from the class
let person1 = new Person('Alice', 25);
let person2 = new Person('Bob', 30);
// Call methods
person1.greet(); // "Hello, I'm Alice and I'm 25 years old."
person2.greet(); // "Hello, I'm Bob and I'm 30 years old."
person1.celebrateBirthday(); // "Happy birthday! Now I'm 26."
console.log(person1.age); // 26
// Access properties
console.log(person1.name); // "Alice"
console.log(person2.name); // "Bob"
// Each instance is independent
console.log(person1 === person2); // false
console.log(person1.name === person2.name); // falseClasses are cleaner and more intuitive
Constructor Function
// Old way: Constructor function
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log('Hello, I am ' + this.name);
};
let person = new Person('Alice', 25);
person.greet();
// Confusing prototype syntax
// Harder to understand
// Methods defined separatelyClass Syntax
// Modern way: Class syntax
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
}
let person = new Person('Alice', 25);
person.greet();
// Clean, organized syntax
// Everything in one place
// Easier to understandConstructor Method
class Product {
constructor(name, price, stock) {
// Initialize properties
this.name = name;
this.price = price;
this.stock = stock;
this.id = Date.now(); // Unique ID
this.createdAt = new Date();
}
getInfo() {
return `${this.name}: $${this.price} (${this.stock} in stock)`;
}
}
let laptop = new Product('Laptop', 999, 5);
console.log(laptop.getInfo()); // "Laptop: $999 (5 in stock)"
console.log(laptop.id);
console.log(laptop.createdAt);
// Constructor with validation
class User {
constructor(username, email) {
// Validate before setting
if (!username || username.length < 3) {
throw new Error('Username must be at least 3 characters');
}
if (!email.includes('@')) {
throw new Error('Invalid email');
}
this.username = username;
this.email = email;
this.createdAt = new Date();
}
}
// Valid user
let user1 = new User('alice', 'alice@example.com');
// Invalid - throws error
try {
let user2 = new User('ab', 'invalid'); // Error!
} catch (error) {
console.log(error.message);
}
// Constructor with default values
class Config {
constructor(options = {}) {
this.theme = options.theme || 'light';
this.language = options.language || 'en';
this.fontSize = options.fontSize || 14;
}
}
let config1 = new Config();
console.log(config1.theme); // "light" (default)
let config2 = new Config({ theme: 'dark', fontSize: 16 });
console.log(config2.theme); // "dark"Instance Methods
class BankAccount {
constructor(accountNumber, balance = 0) {
this.accountNumber = accountNumber;
this.balance = balance;
this.transactions = [];
}
// Deposit money
deposit(amount) {
if (amount <= 0) {
console.log('Amount must be positive');
return false;
}
this.balance += amount;
this.transactions.push({
type: 'deposit',
amount,
date: new Date()
});
console.log(`Deposited $${amount}. New balance: $${this.balance}`);
return true;
}
// Withdraw money
withdraw(amount) {
if (amount <= 0) {
console.log('Amount must be positive');
return false;
}
if (amount > this.balance) {
console.log('Insufficient funds');
return false;
}
this.balance -= amount;
this.transactions.push({
type: 'withdrawal',
amount,
date: new Date()
});
console.log(`Withdrew $${amount}. New balance: $${this.balance}`);
return true;
}
// Get balance
getBalance() {
return this.balance;
}
// Get transaction history
getTransactionHistory() {
return this.transactions;
}
}
// Usage
let account = new BankAccount('12345', 1000);
account.deposit(500); // "Deposited $500. New balance: $1500"
account.withdraw(200); // "Withdrew $200. New balance: $1300"
account.withdraw(2000); // "Insufficient funds"
console.log('Balance:', account.getBalance()); // 1300
console.log('Transactions:', account.getTransactionHistory().length); // 2Getters and Setters
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
// Getter - computed property
get area() {
return this.width * this.height;
}
get perimeter() {
return 2 * (this.width + this.height);
}
// Setter - validation
set width(value) {
if (value <= 0) {
throw new Error('Width must be positive');
}
this._width = value;
}
get width() {
return this._width;
}
set height(value) {
if (value <= 0) {
throw new Error('Height must be positive');
}
this._height = value;
}
get height() {
return this._height;
}
}
let rect = new Rectangle(10, 5);
// Use getters like properties (no parentheses)
console.log(rect.area); // 50
console.log(rect.perimeter); // 30
// Use setters like properties
rect.width = 20;
console.log(rect.area); // 100 (automatically recalculated)
// Setter validation
try {
rect.width = -5; // Error: Width must be positive
} catch (error) {
console.log(error.message);
}
// Practical example: User with computed properties
class User {
constructor(firstName, lastName, birthYear) {
this.firstName = firstName;
this.lastName = lastName;
this.birthYear = birthYear;
}
// Getter for full name
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
// Setter for full name
set fullName(name) {
let parts = name.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
}
// Getter for age
get age() {
return new Date().getFullYear() - this.birthYear;
}
}
let user = new User('Alice', 'Smith', 1998);
console.log(user.fullName); // "Alice Smith"
console.log(user.age); // 26 (in 2024)
user.fullName = 'Bob Jones';
console.log(user.firstName); // "Bob"
console.log(user.lastName); // "Jones"Static Methods
// Static methods belong to the class, not instances
class MathUtils {
// Static method
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
static max(...numbers) {
return Math.max(...numbers);
}
}
// Call on class, not instance
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.multiply(4, 5)); // 20
console.log(MathUtils.max(1, 5, 3, 9)); // 9
// Cannot call on instance
let utils = new MathUtils();
// utils.add(5, 3); // Error! Static methods not on instances
// Practical example: User factory
class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.id = Date.now();
}
// Instance method
getInfo() {
return `${this.name} (${this.email})`;
}
// Static method - create user from JSON
static fromJSON(json) {
let data = JSON.parse(json);
return new User(data.name, data.email);
}
// Static method - validate email
static isValidEmail(email) {
return email.includes('@') && email.includes('.');
}
// Static method - create multiple users
static createBatch(users) {
return users.map(u => new User(u.name, u.email));
}
}
// Use static methods
let json = '{"name":"Alice","email":"alice@test.com"}';
let user = User.fromJSON(json);
console.log(user.getInfo());
console.log(User.isValidEmail('test@example.com')); // true
console.log(User.isValidEmail('invalid')); // false
let users = User.createBatch([
{ name: 'Alice', email: 'alice@test.com' },
{ name: 'Bob', email: 'bob@test.com' }
]);
console.log(users.length); // 2Inheritance (extends)
// Parent class
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
console.log(`${this.name} is eating`);
}
sleep() {
console.log(`${this.name} is sleeping`);
}
getInfo() {
return `${this.name}, age ${this.age}`;
}
}
// Child class - inherits from Animal
class Dog extends Animal {
constructor(name, age, breed) {
super(name, age); // Call parent constructor
this.breed = breed;
}
// Additional method (only for Dog)
bark() {
console.log(`${this.name} says: Woof!`);
}
// Override parent method
getInfo() {
return `${super.getInfo()}, breed: ${this.breed}`;
}
}
// Another child class
class Cat extends Animal {
constructor(name, age, color) {
super(name, age);
this.color = color;
}
meow() {
console.log(`${this.name} says: Meow!`);
}
getInfo() {
return `${super.getInfo()}, color: ${this.color}`;
}
}
// Create instances
let dog = new Dog('Max', 3, 'Labrador');
let cat = new Cat('Whiskers', 2, 'Orange');
// Inherited methods work
dog.eat(); // "Max is eating"
cat.sleep(); // "Whiskers is sleeping"
// Child-specific methods
dog.bark(); // "Max says: Woof!"
cat.meow(); // "Whiskers says: Meow!"
// Overridden methods
console.log(dog.getInfo()); // "Max, age 3, breed: Labrador"
console.log(cat.getInfo()); // "Whiskers, age 2, color: Orange"
// Inheritance chain check
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Cat); // falsePractical Examples
Example 1: Todo List Manager
class Todo {
constructor(text) {
this.id = Date.now() + Math.random();
this.text = text;
this.completed = false;
this.createdAt = new Date();
}
toggle() {
this.completed = !this.completed;
}
edit(newText) {
this.text = newText;
}
}
class TodoList {
constructor(name) {
this.name = name;
this.todos = [];
}
addTodo(text) {
let todo = new Todo(text);
this.todos.push(todo);
return todo;
}
removeTodo(id) {
this.todos = this.todos.filter(t => t.id !== id);
}
toggleTodo(id) {
let todo = this.todos.find(t => t.id === id);
if (todo) {
todo.toggle();
}
}
getCompleted() {
return this.todos.filter(t => t.completed);
}
getPending() {
return this.todos.filter(t => !t.completed);
}
clearCompleted() {
this.todos = this.todos.filter(t => !t.completed);
}
getStats() {
return {
total: this.todos.length,
completed: this.getCompleted().length,
pending: this.getPending().length
};
}
}
// Usage
let myTodos = new TodoList('My Tasks');
myTodos.addTodo('Learn JavaScript');
myTodos.addTodo('Build a project');
myTodos.addTodo('Deploy to production');
let todos = myTodos.todos;
myTodos.toggleTodo(todos[0].id); // Complete first todo
console.log(myTodos.getStats());
// { total: 3, completed: 1, pending: 2 }
myTodos.clearCompleted();
console.log(myTodos.todos.length); // 2Example 2: Shopping Cart System
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;
}
increaseQuantity(amount = 1) {
this.quantity += amount;
}
decreaseQuantity(amount = 1) {
this.quantity = Math.max(0, this.quantity - amount);
}
}
class ShoppingCart {
constructor() {
this.items = [];
}
addItem(product, quantity = 1) {
let existingItem = this.items.find(
item => item.product.id === product.id
);
if (existingItem) {
existingItem.increaseQuantity(quantity);
} else {
this.items.push(new CartItem(product, quantity));
}
}
removeItem(productId) {
this.items = this.items.filter(
item => item.product.id !== productId
);
}
updateQuantity(productId, quantity) {
let item = this.items.find(
item => item.product.id === productId
);
if (item) {
item.quantity = quantity;
}
}
get total() {
return this.items.reduce(
(sum, item) => sum + item.total,
0
);
}
get itemCount() {
return this.items.reduce(
(sum, item) => sum + item.quantity,
0
);
}
clear() {
this.items = [];
}
}
// Usage
let cart = new ShoppingCart();
let laptop = new Product(1, 'Laptop', 999);
let mouse = new Product(2, 'Mouse', 25);
let keyboard = new Product(3, 'Keyboard', 75);
cart.addItem(laptop, 1);
cart.addItem(mouse, 2);
cart.addItem(keyboard, 1);
console.log('Total:', cart.total); // 1124
console.log('Items:', cart.itemCount); // 4
cart.updateQuantity(2, 3); // Update mouse quantity
console.log('New total:', cart.total); // 1149Classes and OOP Mastery
Master object-oriented programming
console.log() to see your output in the console above.Key Takeaways
- Classes are blueprints for creating objects
constructor()initializes new instances- Instance methods are shared by all objects from the class
- Getters/setters provide computed properties and validation
- Static methods belong to the class, not instances
extendscreates inheritancesuper()calls parent constructor or methods- Child classes can override parent methods
instanceofchecks if object is instance of class- OOP principles: encapsulation, inheritance, abstraction, polymorphism
What's Next?
You now understand JavaScript classes and object-oriented programming! You've learned to create classes, use constructors, implement inheritance, and apply OOP principles to build organized, reusable code.
In the next lesson, we'll explore Array and Object Advanced Patterns—learning deep cloning, nested destructuring, and advanced data manipulation techniques!
💪 Practice Challenge:
Before moving on, try:
- Create a class with constructor, methods, and properties
- Implement getters and setters with validation
- Build a parent class and extend it with child classes
- Use static methods for utility functions
- Create a real-world application using multiple classes