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 feedback@compilenrun.com. I’d greatly appreciate your feedback! :)