JavaScript classes, introduced in ECMAScript 2015 (ES6), provide a more intuitive and object-oriented way to create objects and implement inheritance. In this comprehensive guide, we'll dive deep into the basics of class syntax in JavaScript, exploring its features, benefits, and practical applications.

Understanding JavaScript Classes

Classes in JavaScript are primarily syntactical sugar over the existing prototype-based inheritance. They offer a cleaner and more familiar syntax for developers coming from class-based languages like Java or C++.

🔑 Key Point: JavaScript classes don't introduce a new object-oriented inheritance model. They're built on top of prototypes but offer a more intuitive syntax.

Let's start with a basic class declaration:

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

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

In this example, we've defined a Person class with a constructor and a method. Let's break it down:

  1. The class keyword is used to declare a class.
  2. The constructor method is a special method for creating and initializing objects created with the class.
  3. The sayHello method is defined within the class body.

To create an instance of this class, we use the new keyword:

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

Class Expressions

Just like function expressions, we can have class expressions in JavaScript. They can be named or unnamed:

// Unnamed class expression
const Animal = class {
  constructor(name) {
    this.name = name;
  }
};

// Named class expression
const Vehicle = class Car {
  constructor(brand) {
    this.brand = brand;
  }
};

🔍 Note: In named class expressions, the name is only visible within the class's body.

Class Methods

Classes can have two types of methods: instance methods and static methods.

Instance Methods

Instance methods are available on instances of the class. We've already seen an example with the sayHello method in our Person class. Here's another example:

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  calculateArea() {
    return this.width * this.height;
  }

  calculatePerimeter() {
    return 2 * (this.width + this.height);
  }
}

const rect = new Rectangle(5, 3);
console.log(rect.calculateArea()); // Output: 15
console.log(rect.calculatePerimeter()); // Output: 16

Static Methods

Static methods are called on the class itself, not on instances of the class. They're often used for utility functions related to the class.

class MathOperations {
  static add(x, y) {
    return x + y;
  }

  static multiply(x, y) {
    return x * y;
  }
}

console.log(MathOperations.add(5, 3)); // Output: 8
console.log(MathOperations.multiply(4, 2)); // Output: 8

🔑 Key Point: Static methods are called on the class, not on instances. They can't access instance-specific data.

Getters and Setters

Classes in JavaScript also support getters and setters, which allow you to define how a property is accessed or modified.

class Circle {
  constructor(radius) {
    this._radius = radius;
  }

  get diameter() {
    return this._radius * 2;
  }

  set diameter(diameter) {
    this._radius = diameter / 2;
  }

  get area() {
    return Math.PI * this._radius ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.diameter); // Output: 10
circle.diameter = 14;
console.log(circle._radius); // Output: 7
console.log(circle.area.toFixed(2)); // Output: 153.94

In this example:

  • The diameter getter calculates the diameter based on the radius.
  • The diameter setter updates the radius based on a new diameter value.
  • The area getter calculates the area of the circle.

🔍 Note: Getters and setters allow you to execute code on reading or writing a property, giving you more control over how object properties are accessed and modified.

Class Fields

Class fields allow you to add instance properties directly in the class body, without using the constructor. This feature is part of a more recent ECMAScript proposal and is widely supported in modern browsers and transpilers.

class Product {
  name = "Unknown";
  price = 0;

  constructor(name, price) {
    this.name = name;
    this.price = price;
  }

  displayInfo() {
    console.log(`${this.name}: $${this.price}`);
  }
}

const laptop = new Product("Laptop", 999);
laptop.displayInfo(); // Output: Laptop: $999

const unknownProduct = new Product();
unknownProduct.displayInfo(); // Output: Unknown: $0

In this example, name and price are class fields with default values. If no arguments are passed to the constructor, these default values are used.

Private Class Features

JavaScript classes also support private fields, methods, and accessors. These are denoted by a hash (#) prefix and are only accessible within the class body.

class BankAccount {
  #balance = 0;

  constructor(initialBalance) {
    this.#balance = initialBalance;
  }

  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
      this.#logTransaction("Deposit", amount);
    }
  }

  withdraw(amount) {
    if (amount > 0 && amount <= this.#balance) {
      this.#balance -= amount;
      this.#logTransaction("Withdrawal", amount);
    }
  }

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

  #logTransaction(type, amount) {
    console.log(`${type}: $${amount}. New balance: $${this.#balance}`);
  }
}

const account = new BankAccount(1000);
account.deposit(500);
account.withdraw(200);
console.log(account.balance);
// console.log(account.#balance); // This would cause an error

In this example:

  • #balance is a private field.
  • #logTransaction is a private method.
  • The balance getter provides read-only access to the private #balance field.

🔑 Key Point: Private class features provide encapsulation, allowing you to hide implementation details and prevent external code from directly accessing or modifying certain parts of your class.

Inheritance with Classes

One of the main advantages of classes is the clear and intuitive syntax for inheritance. You can create a child class from a parent class using the extends keyword.

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name); // Call the parent constructor
  }

  speak() {
    console.log(`${this.name} barks.`);
  }

  fetch() {
    console.log(`${this.name} fetches the ball.`);
  }
}

const animal = new Animal("Generic Animal");
animal.speak(); // Output: Generic Animal makes a sound.

const dog = new Dog("Buddy");
dog.speak(); // Output: Buddy barks.
dog.fetch(); // Output: Buddy fetches the ball.

In this example:

  • Dog is a subclass of Animal.
  • The super() call in the Dog constructor calls the parent class constructor.
  • The speak method in Dog overrides the method from Animal.
  • The fetch method is unique to the Dog class.

🔍 Note: When overriding a parent method, you can still access the parent method using super.methodName().

The super Keyword

The super keyword is used to call corresponding methods of the parent class. It can be used in two ways:

  1. In constructors: super() calls the parent constructor.
  2. In methods: super.methodName() calls the parent method.
class Vehicle {
  constructor(type) {
    this.type = type;
  }

  describe() {
    return `This is a ${this.type}.`;
  }
}

class Car extends Vehicle {
  constructor(brand) {
    super('car'); // Call parent constructor
    this.brand = brand;
  }

  describe() {
    return `${super.describe()} It's a ${this.brand}.`;
  }
}

const myCar = new Car('Toyota');
console.log(myCar.describe()); // Output: This is a car. It's a Toyota.

Conclusion

JavaScript classes provide a clean, intuitive syntax for creating objects and implementing inheritance. They offer a familiar structure for developers coming from class-based languages while maintaining JavaScript's prototype-based nature under the hood.

Key takeaways from this introduction to JavaScript classes include:

  • Classes are templates for creating objects.
  • The constructor method initializes new instances.
  • Classes can have instance methods, static methods, getters, and setters.
  • Class fields allow for declaring instance properties with default values.
  • Private fields and methods provide encapsulation.
  • Inheritance is implemented using the extends keyword.
  • The super keyword is used to call parent class methods and constructors.

As you continue to work with JavaScript, you'll find that classes offer a powerful way to structure your code, especially for larger, more complex applications. They promote code reusability, maintainability, and provide a clear blueprint for object creation and behavior.

Remember, while classes offer a more traditional object-oriented syntax, JavaScript remains a flexible language that supports multiple programming paradigms. Classes are a tool in your JavaScript toolbox, and understanding when and how to use them effectively will greatly enhance your ability to write clean, efficient, and maintainable code.