JavaScript Classes
Introduction
JavaScript classes, introduced in ECMAScript 2015 (ES6), provide a cleaner and more intuitive syntax for creating objects and implementing inheritance. Although JavaScript is primarily a prototype-based language, classes offer a more familiar, object-oriented approach for developers coming from languages like Java or C#.
Classes act as templates or blueprints for creating objects with predefined properties and methods. They encapsulate data and behavior, making your code more organized, reusable, and maintainable.
Class Syntax Basics
Let's start with the basic syntax of a JavaScript class:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
}
}
// Creating an instance
const john = new Person('John', 30);
console.log(john.greet()); // Output: Hello, my name is John and I am 30 years old.
In this example:
- We define a
Person
class with a constructor and a method - The
constructor
method is called automatically when a new object is created - We create a new instance with the
new
keyword - We can call the
greet()
method on our instance
Constructor Method
The constructor
method is a special method that:
- Gets called automatically when an object is instantiated
- Is used to initialize object properties
- Can accept parameters to set initial values
class Book {
constructor(title, author, year) {
this.title = title;
this.author = author;
this.year = year;
this.isAvailable = true; // Default property
}
}
const myBook = new Book('JavaScript: The Good Parts', 'Douglas Crockford', 2008);
console.log(myBook);
/* Output:
Book {
title: 'JavaScript: The Good Parts',
author: 'Douglas Crockford',
year: 2008,
isAvailable: true
}
*/
Class Methods
Methods in classes are functions that are associated with the class. They're defined without the function
keyword.
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Method to calculate area
calculateArea() {
return this.height * this.width;
}
// Method to calculate perimeter
calculatePerimeter() {
return 2 * (this.height + this.width);
}
// Method with parameters
scale(factor) {
this.height *= factor;
this.width *= factor;
return this;
}
}
const rect = new Rectangle(5, 10);
console.log(rect.calculateArea()); // Output: 50
console.log(rect.calculatePerimeter()); // Output: 30
// Method chaining
rect.scale(2);
console.log(rect.calculateArea()); // Output: 200 (after scaling)
Getters and Setters
JavaScript classes can have getter and setter methods that let you get and set values, with added logic if needed:
class Temperature {
constructor(celsius) {
this._celsius = celsius;
}
// Getter for celsius
get celsius() {
return this._celsius;
}
// Setter for celsius
set celsius(value) {
if (value < -273.15) {
throw new Error('Temperature below absolute zero is not possible');
}
this._celsius = value;
}
// Getter for fahrenheit
get fahrenheit() {
return this._celsius * 9/5 + 32;
}
// Setter for fahrenheit
set fahrenheit(value) {
this.celsius = (value - 32) * 5/9;
}
}
const temp = new Temperature(25);
console.log(temp.celsius); // Output: 25
console.log(temp.fahrenheit); // Output: 77
temp.celsius = 30;
console.log(temp.fahrenheit); // Output: 86
temp.fahrenheit = 68;
console.log(temp.celsius); // Output: 20
Notice how we use an underscore prefix (_celsius
) for the internal property. This is a convention to indicate that the property should be accessed via getters and setters, not directly.
Static Methods and Properties
Static methods and properties belong to the class itself, not to instances of the class. They're useful for utility functions related to the class.
class MathUtils {
static PI = 3.14159;
static square(x) {
return x * x;
}
static cube(x) {
return x * x * x;
}
}
console.log(MathUtils.PI); // Output: 3.14159
console.log(MathUtils.square(4)); // Output: 16
console.log(MathUtils.cube(3)); // Output: 27
// This would throw an error:
// const math = new MathUtils();
// console.log(math.square(4));
Class Inheritance
One of the most powerful features of classes is inheritance, which allows a class to inherit properties and methods from another class:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a noise.`;
}
}
class Dog extends Animal {
constructor(name, breed) {
// Call the parent constructor
super(name);
this.breed = breed;
}
// Override the speak method
speak() {
return `${this.name} barks! Woof woof!`;
}
// Add a new method
fetch() {
return `${this.name} fetches the ball!`;
}
}
const dog = new Dog('Rex', 'German Shepherd');
console.log(dog.name); // Output: Rex
console.log(dog.breed); // Output: German Shepherd
console.log(dog.speak()); // Output: Rex barks! Woof woof!
console.log(dog.fetch()); // Output: Rex fetches the ball!
Key points about inheritance:
- Use the
extends
keyword to create a subclass - Use
super()
in the constructor to call the parent class constructor - Subclasses can override methods from the parent class
- Subclasses can add new methods and properties
Private Fields and Methods
As of more recent JavaScript versions, you can create truly private class fields using the #
prefix:
class BankAccount {
#balance = 0;
#accountNumber;
constructor(accountHolder, initialBalance) {
this.accountHolder = accountHolder;
this.#accountNumber = this.#generateAccountNumber();
if (initialBalance > 0) {
this.deposit(initialBalance);
}
}
#generateAccountNumber() {
return Math.floor(Math.random() * 1000000000);
}
get balance() {
return this.#balance;
}
get accountDetails() {
return {
holder: this.accountHolder,
accountNumber: this.#accountNumber,
balance: this.#balance
};
}
deposit(amount) {
if (amount <= 0) throw new Error('Invalid deposit amount');
this.#balance += amount;
return this.#balance;
}
withdraw(amount) {
if (amount <= 0) throw new Error('Invalid withdrawal amount');
if (amount > this.#balance) throw new Error('Insufficient funds');
this.#balance -= amount;
return this.#balance;
}
}
const account = new BankAccount('Alice Smith', 1000);
console.log(account.balance); // Output: 1000
console.log(account.accountDetails); // Output: {holder: 'Alice Smith', accountNumber: (some number), balance: 1000}
account.deposit(500);
console.log(account.balance); // Output: 1500
account.withdraw(200);
console.log(account.balance); // Output: 1300
// These would throw errors:
// console.log(account.#balance);
// console.log(account.#accountNumber);
// account.#generateAccountNumber();
Private fields and methods are not accessible outside the class, providing true encapsulation.
Real-World Example: Shopping Cart
Let's create a more comprehensive example that models a shopping cart system:
class Product {
constructor(id, name, price) {
this.id = id;
this.name = name;
this.price = price;
}
getFormattedPrice() {
return `$${this.price.toFixed(2)}`;
}
}
class CartItem {
constructor(product, quantity = 1) {
this.product = product;
this.quantity = quantity;
}
getTotalPrice() {
return this.product.price * this.quantity;
}
}
class ShoppingCart {
#items = [];
addItem(product, quantity = 1) {
const existingItem = this.#items.find(item => item.product.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
this.#items.push(new CartItem(product, quantity));
}
}
removeItem(productId) {
const index = this.#items.findIndex(item => item.product.id === productId);
if (index !== -1) {
this.#items.splice(index, 1);
return true;
}
return false;
}
updateQuantity(productId, quantity) {
if (quantity <= 0) {
return this.removeItem(productId);
}
const item = this.#items.find(item => item.product.id === productId);
if (item) {
item.quantity = quantity;
return true;
}
return false;
}
getItems() {
return [...this.#items];
}
getTotalPrice() {
return this.#items.reduce((total, item) => total + item.getTotalPrice(), 0);
}
getItemCount() {
return this.#items.reduce((count, item) => count + item.quantity, 0);
}
clear() {
this.#items = [];
}
}
// Usage example:
const laptop = new Product(1, 'Laptop', 999.99);
const phone = new Product(2, 'Smartphone', 699.99);
const headphones = new Product(3, 'Headphones', 149.99);
const cart = new ShoppingCart();
cart.addItem(laptop);
cart.addItem(phone, 2);
cart.addItem(headphones, 3);
console.log('Cart items:', cart.getItems());
console.log('Total price:', cart.getTotalPrice().toFixed(2)); // Expected: 2999.94
console.log('Item count:', cart.getItemCount()); // Expected: 6
cart.updateQuantity(2, 1); // Update phone quantity to 1
console.log('Updated item count:', cart.getItemCount()); // Expected: 5
console.log('Updated total price:', cart.getTotalPrice().toFixed(2)); // Expected: 2299.95
cart.removeItem(3); // Remove headphones
console.log('Final item count:', cart.getItemCount()); // Expected: 2
console.log('Final total price:', cart.getTotalPrice().toFixed(2)); // Expected: 1699.98
This example demonstrates many features of classes working together:
- Multiple classes interacting (Product, CartItem, ShoppingCart)
- Private fields (the cart's items array)
- Methods with different responsibilities
- Data encapsulation
Summary
JavaScript classes provide a clean, intuitive syntax for creating object-oriented code in JavaScript. They offer:
- A structured way to create objects with consistent properties and methods
- Constructor functions for setting up new objects
- Instance methods for object-specific functionality
- Static methods and properties for class-level utilities
- Inheritance for creating hierarchies of related classes
- Private fields and methods for proper encapsulation
- Getters and setters for controlled property access
While JavaScript's class syntax is syntactic sugar over its prototype-based inheritance, it provides a more familiar pattern for developers from traditional object-oriented backgrounds and helps organize code in larger applications.
Exercises
-
Create a
Circle
class with properties for radius and methods to calculate area and circumference. -
Create a
User
class with properties like username and email, then extend it to create anAdmin
class with additional permissions. -
Build a simple library system with
Book
,Author
, andLibrary
classes that interact with each other. -
Implement a
Timer
class that can start, stop, reset, and report elapsed time. -
Create a
Vehicle
class hierarchy with different types of vehicles that inherit from a base class.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)