Mastering JavaScript Classes: Your Shortcut to OOP Success!
Tags: webdev, javascript, typescript, programming
Published: 2023-09-08 19:29:45

A class is a blueprint for creating objects, it defines properties and methods that the objects created by that class will have. The introduction of classes in ECMAScript (ES6) aimed to provide a more structured and object-oriented way to create and manage objects.

In this article, we will cover the following topics:

- How to create a class.
- Utilizing constructors in classes.
- Static methods and properties
- Getters and setters
- Inheritance
- How to leverage TypeScript for better classes

### Syntactic Sugar

We could create objects with prototypes in the past, but the syntax was too verbose. As a result, classes were introduced, and the new class simply inherits what prototypes are and their methods.

Before ES6:

```js
function Book(title, author, releaseYear){
  this.title = title;
  this.author = author;
  this.releaseYear = releaseYear;
}

Book.prototype.getOverview = function(){
  return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
}

const book = new Book("Atomic Habits", "James Clear", 2018);

console.log(book.getOverview());
```

After ES6:

```js
class Book {
  constructor(title, author, releaseYear){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
  }

  getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}

const book = new Book("Atomic Habits", "James Clear", 2018);

console.log(book.getOverview());
```

### Defining a class

To define a class we have to use the class keyword followed by its name and curly braces.

```js
class Book {}
```

A class can have a `constructor`, a special method within a class that is automatically called when an instance of the class or object is created. The purpose of a constructor is to initialize the object’s state or perform any setup operations required for the object to function properly. In our example, we assign properties to our class that we can access later.

```js
class Book {
  constructor(title, author, releaseYear){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
  }
}
```

Note: Classes can have only one `constructor`. JavaScript doesn’t support constructor or method overloading.

Classes can hold properties, primitive or non-primitive types, and methods (functions). While it’s possible to explicitly add properties to a class outside of the `constructor`, the recommended approach is to assign them directly within the `constructor`.

```js
class Book {
  // This is possible, but redundant in this scenario since we've already defined these properties within the constructor.
  title;
  author;
  releaseYear;
  constructor(title, author, releaseYear){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
  }
}
```

To define methods in classes, we utilize a similar syntax to functions but without the need for the `function` keyword.

```js
class Book {
  constructor(title, author, releaseYear){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
  }
  
  // We omit the function keyword
  getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}
```

To make a class useful, we need to instantiate an object of that class by using the `new` keyword followed by calling the `constructor` with the correct arguments:

```js
const book = new Book("Atomic Habits", "James Clear", 2018);
```

If we do not provide a constructor, internally it will be defaulted to:

```js
constructor(){}
```

Note: A class is not hoisted to the top, unlike functions. To avoid errors, declare them before instantiating objects of that type.

### Static keyword

A `static` method or property can be accessed directly from the class itself, without needing to create an instance of the class. This feature is particularly valuable for utility functions or constant variables.

```js
class Utilities {
  static PI = 3.14;

  static max(a, b){
    return a > b ? a : b;
  }
}

console.log(Utilities.PI); // Output: 3.14
console.log(Utilities.max(1,2)); //Output: 2
```

If you’re acquainted with the JavaScript `Math` class, understanding this concept becomes more straightforward.

## Private properties

A private property or method can’t be accessed from outside the class. To define such a property or method we use `#` symbol.

```js
class Book {
  #ISBN = 5;
  constructor(title, author, releaseYear, price){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
    this._price = price;
  }

  #getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}

const book = new Book("Atomic Habits", "James Clear", 2018, 120);

//This will throw an error: "getOverview is not a function"
book.getOverview();

//This will return undefined
book.ISBN;
```

### Getters and setters

Properties offer a flexible way to both retrieve and set values without the need for direct access to the underlying data. This flexibility enhances code maintainability and encapsulation.

We can enhance our `Book` class by introducing a private property named `price`. To safeguard it from invalid values, we'll use a setter method, while retaining the ability to access it using a getter method.

```js
class Book {
  constructor(title, author, releaseYear, price){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
    this._price = price;
  }
  
  // Getter
  get price() {
    return this._price;
  }

  // Setter
  set price(newPrice){
    if(this._price === 0) throw new Error("Price can't be zero");
    this._price = newPrice;
  }
}

// Usage

const book = new Book("Atomic Habits", "James Clear", 2018, 120);

console.log(`Price: $${book.price}`);

book.price = 10;

console.log(`Price: $${book.price}`);
```

It’s worth noting that we didn’t invoke the setter method explicitly; instead, we utilized direct assignment. The same applies to the getter method. This feature offers a convenient and elegant approach to working with our class properties.

### Inheritance

To begin, it’s essential to grasp the fundamental concepts of superclass and derived classes.

A superclass, also known as a base class or parent class, is a class from which other classes inherit properties and methods

A derived class, also known as a subclass or child class, is a class that inherits attributes and methods from a superclass

Inheritance is a fundamental concept in object-oriented programming, enabling code reuse, extensibility, and the organization of classes into hierarchies.

For example, we want to create a `Technical Book` that has an edition property. We know that a `Technical Book` is a `Book`, so we extend from `Book` to make its properties available to our new class.

```js
class Book {
  constructor(title, author, releaseYear, price){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
    this._price = price;
  }
}

class TechnicalBook extends Book {
  constructor(title, author, releaseYear, price, edition){
    super(title, author, releaseYear, price);
    this.edition = edition;
  }
}

const technicalBook = new TechnicalBook("Atomic Habits", "James Clear", 2018, 120, 1);

console.log(`Edition ${technicalBook.edition} of the book ${technicalBook.title}`);
```

We use the `super` keyword to call the constructor of the superclass `Book` from our derived class `TechnicalBook`. This is done to access and execute the behavior defined in the superclass, which can then be extended or customized in the derived class.

## Using Typescript

TypeScript is a statically typed superset of JavaScript that supports classes for object-oriented programming.

### Access modifiers

There are 3 access modifiers:

- `public` — properties and methods can be accessed outside the class.
- `private` — properties and methods can only be accessed inside the class. Derived classes do not have access to these properties or methods.
- `protected` — properties and methods can only be accessed inside the class or by derived classes.

By default all methods and properties are private.

```ts
class Book implements IBook {
  public title: string;
  private author: string;
  protected releaseYear: number;

  public constructor(title: string, author: string, releaseYear: number){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
  }

  public getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}
```

Take a look at this clever technique using access modifiers to directly initialize properties within the constructor’s parameter list.

```ts
class Book {
  public constructor(public title: string, private author: string, protected releaseYear: number){}

  public getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}
```

### Interfaces

An interface serves as a contract that a class must adhere to. It obliges the class to implement the specified properties and methods outlined in the interface. This enforces a clear set of rules and expectations for how the class should behave.

```ts
interface IBook {
  title: string;
  author: string;
  releaseYear: number;
  getOverview: () => string;
}

class Book implements IBook {
  public title: string;
  public author: string;
  public releaseYear: number;

  public constructor(title: string, author: string, releaseYear: number){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
  }

  getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}
```

### Abstract class

In TypeScript, an abstract class is a class that cannot be instantiated on its own but serves as a blueprint for other classes. Abstract classes allow you to define common methods and properties that must be implemented by derived classes. To create an abstract class in TypeScript, you use the `abstract` keyword. Here's a basic example:

```ts
abstract class Shape {
  abstract calculateArea(): number;

  displayArea() {
    console.log(`Area: ${this.calculateArea()}`);
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }

  calculateArea(): number {
    return Math.PI * Math.pow(this.radius, 2);
  }
}

const circle = new Circle(5);

circle.displayArea(); // Output: Area: 78.53981633974483
```

If you found this article interesting, please consider leaving a follow or a reaction. Your feedback motivates me to create more content. If you have any questions or specific topics you’d like me to write about, please feel free to leave a comment with your suggestions. I appreciate your support and input!