Skip to main content

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:

javascript
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
javascript
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.

javascript
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:

javascript
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.

javascript
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:

javascript
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:

javascript
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:

javascript
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

  1. Create a Circle class with properties for radius and methods to calculate area and circumference.

  2. Create a User class with properties like username and email, then extend it to create an Admin class with additional permissions.

  3. Build a simple library system with Book, Author, and Library classes that interact with each other.

  4. Implement a Timer class that can start, stop, reset, and report elapsed time.

  5. 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! :)