JavaScript Classes and Objects
Classes and objects are core concepts of Object-Oriented Programming (OOP). JavaScript introduced class syntax starting from ES6, making object-oriented programming more intuitive and easier to understand. In this chapter, we will learn about classes and objects in JavaScript.
What is Object-Oriented Programming
Object-Oriented Programming (OOP) is a programming paradigm that uses objects to organize code. The core concepts of OOP include:
- Encapsulation: Combining data and methods that operate on the data
- Inheritance: Child classes can inherit properties and methods from parent classes
- Polymorphism: Same interface can have different implementations
- Abstraction: Hiding complex implementation details, exposing only necessary interfaces
Object Basics
Object Literals
In JavaScript, objects are collections of properties and methods:
javascript
// Object literal
const person = {
name: "John",
age: 25,
greet: function() {
return `Hello, I'm ${this.name}`;
}
};
console.log(person.name); // "John"
console.log(person.greet()); // "Hello, I'm John"Object Property Access
javascript
const user = {
name: "Jane",
"full-name": "Jane Doe",
age: 30
};
// Dot notation
console.log(user.name); // "Jane"
// Bracket notation
console.log(user["full-name"]); // "Jane Doe"
console.log(user["age"]); // 30
// Dynamic property access
const propertyName = "name";
console.log(user[propertyName]); // "Jane"Object Methods
javascript
const calculator = {
result: 0,
add: function(num) {
this.result += num;
return this;
},
subtract: function(num) {
this.result -= num;
return this;
},
getResult: function() {
return this.result;
},
reset: function() {
this.result = 0;
return this;
}
};
// Method chaining
const result = calculator.add(10).subtract(3).getResult();
console.log(result); // 7ES6 Class Syntax
Basic Class Definition
javascript
// Define a simple class
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}, ${this.age} years old`;
}
// Static method
static getSpecies() {
return "Human";
}
}
// Create class instances
const person1 = new Person("John", 25);
const person2 = new Person("Jane", 30);
console.log(person1.greet()); // "Hello, I'm John, 25 years old"
console.log(Person.getSpecies()); // "Human"Constructor
javascript
class Rectangle {
constructor(width, height) {
// Parameter validation
if (width <= 0 || height <= 0) {
throw new Error("Width and height must be greater than 0");
}
this.width = width;
this.height = height;
}
// Calculate area
getArea() {
return this.width * this.height;
}
// Calculate perimeter
getPerimeter() {
return 2 * (this.width + this.height);
}
// Get description
getDescription() {
return `This is a ${this.width}x${this.height} rectangle`;
}
}
const rect = new Rectangle(10, 5);
console.log(rect.getArea()); // 50
console.log(rect.getPerimeter()); // 30Getter and Setter
javascript
class User {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this._age = 0; // Private property convention
}
// Getter
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
get age() {
return this._age;
}
// Setter
set age(value) {
if (value < 0) {
throw new Error("Age cannot be negative");
}
this._age = value;
}
// Read-only property
get species() {
return "Human";
}
}
const user = new User("John", "Doe");
console.log(user.fullName); // "John Doe"
user.age = 25;
console.log(user.age); // 25Private Properties and Methods (ES2022)
javascript
class BankAccount {
// Private properties
#balance = 0;
#accountNumber;
constructor(accountNumber, initialBalance = 0) {
this.#accountNumber = accountNumber;
this.#balance = initialBalance;
}
// Public methods
deposit(amount) {
if (amount <= 0) {
throw new Error("Deposit amount must be greater than 0");
}
this.#balance += amount;
return this.#balance;
}
withdraw(amount) {
if (amount <= 0) {
throw new Error("Withdrawal amount must be greater than 0");
}
if (amount > this.#balance) {
throw new Error("Insufficient balance");
}
this.#balance -= amount;
return this.#balance;
}
getBalance() {
return this.#balance;
}
// Private method
#validateTransaction(amount) {
return amount > 0 && amount <= this.#balance;
}
// Public method can call private method
transfer(amount, targetAccount) {
if (this.#validateTransaction(amount)) {
this.withdraw(amount);
targetAccount.deposit(amount);
return true;
}
return false;
}
}
const account1 = new BankAccount("123456", 1000);
const account2 = new BankAccount("789012", 500);
account1.deposit(200);
console.log(account1.getBalance()); // 1200
account1.withdraw(100);
console.log(account1.getBalance()); // 1100Class Inheritance
Basic Inheritance
javascript
// Parent class
class Animal {
constructor(name, species) {
this.name = name;
this.species = species;
}
speak() {
return `${this.name} makes a sound`;
}
getInfo() {
return `I'm a ${this.species} named ${this.name}`;
}
}
// Child class
class Dog extends Animal {
constructor(name, breed) {
super(name, "Dog"); // Call parent constructor
this.breed = breed;
}
// Override parent method
speak() {
return `${this.name} barks`;
}
// Child-specific method
fetch() {
return `${this.name} fetches the ball`;
}
}
class Cat extends Animal {
constructor(name, color) {
super(name, "Cat");
this.color = color;
}
speak() {
return `${this.name} meows`;
}
climb() {
return `${this.name} climbs the tree`;
}
}
const dog = new Dog("Buddy", "Golden Retriever");
const cat = new Cat("Whiskers", "Orange");
console.log(dog.speak()); // "Buddy barks"
console.log(dog.fetch()); // "Buddy fetches the ball"
console.log(dog.getInfo()); // "I'm a Dog named Buddy"
console.log(cat.speak()); // "Whiskers meows"
console.log(cat.climb()); // "Whiskers climbs the tree"Static Properties and Methods
javascript
class MathUtils {
// Static property (ES2022)
static PI = 3.14159;
// Static methods
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
static circleArea(radius) {
return this.PI * radius * radius; // Use static property
}
static isEven(number) {
return number % 2 === 0;
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.circleArea(5)); // 78.53975
console.log(MathUtils.isEven(4)); // trueObject Prototype
Prototype Chain
javascript
// Constructor function (ES5 way)
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
Person.prototype.getAge = function() {
return this.age;
};
const person = new Person("John", 25);
console.log(person.greet()); // "Hello, I'm John"
console.log(person.getAge()); // 25
// Check prototype chain
console.log(person.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // trueObject.create()
javascript
// Create object using Object.create()
const animal = {
species: "Animal",
speak: function() {
return `${this.name} makes a sound`;
}
};
const dog = Object.create(animal);
dog.name = "Buddy";
dog.breed = "Golden Retriever";
console.log(dog.speak()); // "Buddy makes a sound"
console.log(dog.species); // "Animal"
// Create object without prototype
const obj = Object.create(null);
obj.name = "John";
// console.log(obj.toString); // undefinedObject Methods
Object.keys(), Object.values(), Object.entries()
javascript
const user = {
name: "John",
age: 25,
city: "New York"
};
// Get all keys
console.log(Object.keys(user)); // ["name", "age", "city"]
// Get all values
console.log(Object.values(user)); // ["John", 25, "New York"]
// Get key-value pairs array
console.log(Object.entries(user));
// [["name", "John"], ["age", 25], ["city", "New York"]]
// Iterate over object
Object.entries(user).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});Object.assign()
javascript
// Object merging
const target = { a: 1, b: 2 };
const source1 = { b: 4, c: 5 };
const source2 = { c: 6, d: 7 };
const result = Object.assign(target, source1, source2);
console.log(result); // { a: 1, b: 4, c: 6, d: 7 }
// Shallow copy
const original = { name: "John", age: 25 };
const copy = Object.assign({}, original);
copy.age = 26;
console.log(original.age); // 25
console.log(copy.age); // 26Object Destructuring
javascript
const user = {
name: "John",
age: 25,
address: {
city: "New York",
district: "Manhattan"
},
hobbies: ["reading", "swimming"]
};
// Basic destructuring
const { name, age } = user;
console.log(name, age); // "John" 25
// Renaming
const { name: userName, age: userAge } = user;
console.log(userName, userAge); // "John" 25
// Default values
const { name: n, gender = "Unknown" } = user;
console.log(n, gender); // "John" "Unknown"
// Nested destructuring
const { address: { city, district } } = user;
console.log(city, district); // "New York" "Manhattan"
// Array destructuring
const { hobbies: [firstHobby, secondHobby] } = user;
console.log(firstHobby, secondHobby); // "reading" "swimming"Design Patterns
Factory Pattern
javascript
class UserFactory {
static createUser(type, name, ...args) {
switch (type) {
case "admin":
return new AdminUser(name, ...args);
case "customer":
return new CustomerUser(name, ...args);
case "guest":
return new GuestUser(name);
default:
throw new Error("Unknown user type");
}
}
}
class User {
constructor(name) {
this.name = name;
this.createdAt = new Date();
}
getInfo() {
return `User: ${this.name}, Created: ${this.createdAt}`;
}
}
class AdminUser extends User {
constructor(name, permissions = []) {
super(name);
this.role = "admin";
this.permissions = permissions;
}
getInfo() {
return `${super.getInfo()}, Role: ${this.role}, Permissions: ${this.permissions.join(", ")}`;
}
}
class CustomerUser extends User {
constructor(name, customerId) {
super(name);
this.role = "customer";
this.customerId = customerId;
}
}
class GuestUser extends User {
constructor(name) {
super(name || "Guest");
this.role = "guest";
}
}
// Use factory to create different user types
const admin = UserFactory.createUser("admin", "Admin", ["read", "write", "delete"]);
const customer = UserFactory.createUser("customer", "Customer", "CUST001");
const guest = UserFactory.createUser("guest", "Guest");
console.log(admin.getInfo());
console.log(customer.getInfo());
console.log(guest.getInfo());Singleton Pattern
javascript
class Logger {
constructor() {
if (Logger.instance) {
return Logger.instance;
}
this.logs = [];
this.level = "info";
Logger.instance = this;
return this;
}
static getInstance() {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
setLevel(level) {
this.level = level;
}
log(message, level = "info") {
if (this.shouldLog(level)) {
const logEntry = {
timestamp: new Date().toISOString(),
level: level,
message: message
};
this.logs.push(logEntry);
console.log(`[${logEntry.timestamp}] [${logEntry.level.toUpperCase()}] ${logEntry.message}`);
}
}
info(message) {
this.log(message, "info");
}
warn(message) {
this.log(message, "warn");
}
error(message) {
this.log(message, "error");
}
shouldLog(level) {
const levels = { debug: 0, info: 1, warn: 2, error: 3 };
return levels[level] >= levels[this.level];
}
getLogs() {
return this.logs;
}
}
// Use singleton
const logger1 = Logger.getInstance();
const logger2 = Logger.getInstance();
console.log(logger1 === logger2); // true
logger1.info("This is the first log");
logger2.warn("This is the second log");
logger1.error("This is the third log");
console.log(logger1.getLogs().length); // 3
console.log(logger2.getLogs().length); // 3Shopping Cart Example
javascript
class ShoppingCart {
constructor() {
this.items = [];
this.discount = 0;
}
addItem(product, quantity = 1) {
const existingItem = this.items.find(item => item.product.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
this.items.push({ product, quantity });
}
return this;
}
removeItem(productId) {
this.items = this.items.filter(item => item.product.id !== productId);
return this;
}
getTotalPrice() {
const subtotal = this.items.reduce((total, item) => {
return total + (item.product.price * item.quantity);
}, 0);
return subtotal * (1 - this.discount);
}
setDiscount(discount) {
if (discount < 0 || discount > 1) {
throw new Error("Discount must be between 0 and 1");
}
this.discount = discount;
return this;
}
getItemsCount() {
return this.items.reduce((count, item) => count + item.quantity, 0);
}
clear() {
this.items = [];
this.discount = 0;
return this;
}
}
// Usage example
const cart = new ShoppingCart();
const product1 = { id: 1, name: "Laptop", price: 999 };
const product2 = { id: 2, name: "Mouse", price: 29 };
cart.addItem(product1, 1)
.addItem(product2, 2)
.setDiscount(0.1); // 10% discount
console.log(cart.getTotalPrice()); // 951.3
console.log(cart.getItemsCount()); // 3Summary
Key points about JavaScript classes and objects:
- Object Basics: Collections of properties and methods, created with literals or constructors
- ES6 Class Syntax: Use class keyword to define classes, constructor for initialization
- Getter and Setter: Special methods for accessing and setting properties
- Private Properties: Use # prefix for private properties and methods
- Inheritance: Use extends keyword for class inheritance, super to call parent
- Static Properties and Methods: Belong to the class itself, not instances
- Prototype Chain: The foundation of JavaScript object inheritance
- Object Methods: Object.keys(), Object.values(), Object.entries(), etc.
- Design Patterns: Factory pattern, singleton pattern, etc.
Mastering classes and objects is fundamental for learning object-oriented programming. In the next chapter, we will learn about JavaScript class inheritance.