Table of content

Deep Dive: Classes in JavaScript

Defining a Class

Inheritance

Static Methods and Properties

Private and Protected Members

Mastering JavaScript Classes: A Comprehensive Guide

November 7, 2024

Dive deep into the power of classes in JavaScript. Explore how to define classes, implement inheritance, utilize static methods and properties, and manage access control with private and protected members. This comprehensive guide will take your JavaScript object-oriented programming skills to the next level.

Deep Dive: Classes in JavaScript

JavaScript, being a prototype-based language, has traditionally used functions and prototype-based inheritance to create objects and implement inheritance. However, with the introduction of ES6 (ECMAScript 2015), JavaScript gained support for classes, which provide a more familiar and class-based object-oriented programming (OOP) syntax.

Defining a Class

The basic syntax for defining a class in JavaScript is:

class ClassName {
  constructor(param1, param2, ...) {
    // initializing properties
  }

  method1( /* parameters */ ) {
    // method implementation
  }

  method2( /* parameters */ ) {
    // method implementation
  }

  // more methods...
}

The class keyword is used to define a new class. Inside the class, the constructor method is a special method that is called when you create a new instance of the class using the new keyword. The constructor is used to initialize the object's properties.

In addition to the constructor, you can define any number of methods within the class. These methods are available to all instances of the class.

Here's an example of a simple Person class:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
 }

  greet() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

const john = new Person('John', 30);
john.greet(); // Output: Hello, my name is John and I'm 30 years old.

Inheritance

One of the key features of classes in JavaScript is their support for inheritance. Inheritance allows you to create new classes based on existing ones, inheriting their properties and methods.

To create a subclass that inherits from a parent class, you use the extends keyword:

class Student extends Person {
  constructor(name, age, grade) {
    super(name, age);
    this.grade = grade;
  }

  study() {
    console.log(`${this.name} is studying.`);
  }
}

const jane = new Student('Jane', 18, 'A');
jane.greet(); // Output: Hello, my name is Jane and I'm 18 years old.
jane.study(); // Output: Jane is studying.

In this example, the Student class inherits from the Person class. The Student class has its own constructor method that calls the constructor of the Person class using the super keyword, and adds a grade property. The Student class also has its own study method.

When you create a new Student instance, it inherits all the properties and methods from the Person class, as well as the new ones defined in the Student class.

Static Methods and Properties

In addition to instance methods and properties (which are accessible on the object instances), classes in JavaScript also support static methods and properties. These are methods and properties that are accessible directly on the class itself, rather than on instances of the class.

class Math {
  static PI = 3.14159;

  static add(a, b) {
    return a + b;
  }
}

console.log(Math.PI); // Output: 3.14159
console.log(Math.add(2, 3)); // Output: 5

In this example, the PI property and the add method are both static, so they can be accessed directly on the Math class, without creating an instance of it.

Static methods and properties are useful for providing utility functions or constants that are related to the class but don't necessarily need to be tied to a specific instance of the class.

Private and Protected Members

In addition to public members (properties and methods that are accessible from anywhere), classes in JavaScript also support private and protected members. These are members that are only accessible within the class or its subclasses, respectively.

To define a private member, you can use the # prefix:

class BankAccount {
  #balance = 0;

  deposit(amount) {
    this.#balance += amount;
  }

  withdraw(amount) {
    if (amount <= this.#balance) {
      this.#balance -= amount;
      return true;
    }
    return false;
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount();
account.deposit(1000);
console.log(account.getBalance()); // Output: 1000
console.log(account.#balance); // Error: Private field '#balance' must be declared in an enclosing class

In this example, the #balance property is private, and can only be accessed and modified through the deposit, withdraw, and getBalance methods.

Protected members, on the other hand, are accessible within the class and its subclasses. To define a protected member, you can use a convention like a leading underscore (_), although this is not enforced by the language.

class BankAccount {
  _balance = 0;

  deposit(amount) {
    this._balance += amount;
  }

  withdraw(amount) {
   if (amount <= this._balance) {
     this._balance -= amount;
     return true;
   }
   return false;
  }

  getBalance() {
   return this._balance;
  }
}

class CheckingAccount extends BankAccount {
  withdrawFee = 5;

  withdraw(amount) {
    if (super.withdraw(amount + this.withdrawFee)) {
      console.log(`Withdrew ${amount} with a $${this.withdrawFee} fee.`);
      return true;
    }
    return false;
  }
}

const account = new CheckingAccount();
account.deposit(1000);
account.withdraw(50); // Output: Withdrew 50 with a $5 fee.

In this example, the _balance property is protected, and can be accessed and modified by the BankAccount class and its subclasses, like the CheckingAccount class.

Overall, classes in JavaScript provide a powerful way to create and manage complex object-oriented structures, with support for inheritance, static members, and access control.