Object Oriented Programming in JavaScript
Tags: javascript
Published: 2023-04-26 14:52:20

*This article was originally written by [Adebayo Adams](https://www.honeybadger.io/blog/authors/adebayoadams/) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/javascript-oop/).*

Much of what you do when writing software is creating and connecting multiple values and methods that work together to provide your application's functionality. Object-oriented programming helps you achieve and implement this much more easily and declaratively.

In this article, you will learn everything you need to know to start using classes and object-oriented programming approaches in JavaScript.

Let's examine what you need to get the most out of this article.

## Prerequisites

To follow this tutorial, you need to have basic knowledge of JavaScript.

## Object-Oriented Programming

[Object-oriented programming (OOP)](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_programming) is a programming paradigm used by developers to structure software applications into reusable pieces of code and blueprints using objects. OOP is used daily in large, complex, and actively maintained projects.

OOP makes it easier to create and manage many parts of your application in isolation and connect them without making them depend on one another. Next, let's look at the four major concepts of OOP.

### Abstraction

Abstraction in OOP is the process of exposing only the necessary functions to the user while hiding the complex inner workings to make the programs easier to use and understand.

For example, when you send a message on your phone, all the functions and logic that routes your message to the other person are hidden because you don't need to know them or how they work.

Similarly, in programming, if you are building an API that helps financial apps verify users identity and banking information, the developers that use your API don't need to know which database system you use or how you make calls to your database. All they need to know is the function they need to call and what parameters they need to provide.

Abstraction helps to reduce complexity, increases usability, and makes changes to the application less disruptive.

### Encapsulation

Encapsulation is the process of bundling related code into a single unit. Encapsulation makes it impossible for other parts of the code to manipulate or change how the bundled part of the application works unless you explicitly go into that unit and change them.

For example, if you are building a flight-booking API, it makes sense to separate the code that searches for the flight from the code that books the flight. This way, two different developers can work on each part seamlessly without conflicts because each developer will have no reason to manipulate the other's code directly.

Encapsulation helps you reduce complexity and increases code reusability.

### Inheritance

Inheritance in OOP reduces code duplication, enabling you to build a part of your application on another by inheriting properties and methods from that part of the application.

For example, when building an e-commerce delivery app with multiple types of vehicles, the `Car` and `Motorcycle` classes can inherit `pickUp` and `dropOff` functions from the `Vehicle` class. Classes will be explained in more detail later in the article.

A significant advantage of inheritance in OOP is the reduction of code duplication.

### Polymorphism

In programming, polymorphism is a term used to describe a code or program that can handle many types of data by returning a response or result based on the given data.

For example, you have a form that is used to add products to a product catalog and three different types of products. With polymorphism, you can create a single class method to format all kinds of products before adding them to the database.

Polymorphism helps you eliminate complex and unnecessary `if` and `switch` statements, as they can become lengthy when writing complex programs.

Let's look at JavaScript objects in the next section.

## JavaScript Objects

An object in JavaScript is an unordered collection of key-value pairs, also known as properties and values. Object keys can be a string value, while the values can be any type. Here are some examples:

- String
- Number
- Boolean
- Array
- Function

Next, let's look at how to create objects in JavaScript.

### Creating Objects

Creating an object in JavaScript is fairly easy:

```js
const car = {
    name: 'Ford',
    year: 2015,
    color: 'red',
    description: function () {
    return `${this.name} - ${this.year} - ${this.color}`;
    }
} 
```

The code above declares a `car` object with `name`, `year`, `color`, and a `description` function as its properties.

### Accessing Object Properties

There are two ways to access an object property in JavaScript; let's look at them below:

### Using the Dot Notation

The following example shows how to access object properties using dot notation.

```js
const country = {
    name: 'Spain',
    population: 4000000,
    description: function () {
    return `${this.name} - ${this.population}`;
    }
}
```

If you have an object like the one shown above, you can use the format `objectName.keyName`, which should return the value of the given key:

```js
console.log(country.name); // returns 'Spain'
```

### Using the Array Notation

The following example shows how to access object properties using the array notation.

```js
const job = {
  role: "Software Engineer",
  'salary': 200000,
  applicationLink: "meta.com/careers/SWE-role/apply",
  isRemote: true,
};
```

If you have an object like the one above, you can use the format `objectName[keyName],` which should return the value of the given key:

```js
console.log(job[role]); // returns 'Software Engineer'
```

Additionally, you can only access the `salary` property using the array notation. Trying to get it with the dot notation will return an error:

```js
console.log(job.'salary'); // SyntaxError: Unexpected string
```

Next, let's see how to modify object properties.

### Modifying Object Properties

You can dynamically add, edit, and delete object properties in JavaScript.

### Editing Properties

You can use the assignment `=` operator to modify object values. Here is an example:

```js
const person = {
  name: "John",
  age: 30,
  job: "Software Developer",
  country: "Nigeria",
  car: "Ford",
  description: function () {
    return `${this.name} - ${this.age} - ${this.job.role} - ${this.country.name} - ${this.car.name}`;
  },
};
```

You can also change the value of `name` in the above object:

```js
person.name = "Smith Doe";
console.log(person.name); // returns "Smith Doe"
```

### Adding New Properties

One of the significant differences between objects in other languages and objects in JavaScript is the ability to add a new property to an object after creation.

To add a new property to an object, you use the dot notation:

```js
// adding a new `race` property
person.race = "Asian";
console.log(person.race); // returns "Asian"
```

The code above adds a new `race` property with the value "Asian".

### Deleting Object Properties

JavaScript allows you to delete properties from an object by using the `delete` keyword:

```js
delete person.race;
console.log(person.race); // returns 'undefined'
```

The code above deletes the `race` property, and accessing the `race` property will return undefined.

> Note: You can only delete existing object properties.

### Checking Properties

Before adding to or deleting properties from an object, it is an excellent idea to determine whether the property exists on the object. This seemingly simple check will save you hours of debugging a bug caused by duplicate values.

To determine whether a property exists on an object, you can use the `in` keyword:

```js
console.log('name' in person) // returns true
console.log('race' in person) // returns false
```

The code above returns `true` for the name check because the name exists and `false` for the deleted `race` property.

Now that you know what objects are and how to use them, let's take the next step to OOP in JavaScript by learning about classes.

## Classes

In programming, a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) is a structure defined by a programmer that is then used to create multiple objects of the same type. For example, if you are building an application that handles various cars, you can create a `Car` class that has the essential functions and properties that apply to all vehicles. Then, you can use this class to make other cars and add properties and methods that are specific to each vehicle to them.

To extend the objects you saw in the previous example, if you wanted to create another job object, you would need to create something like this:

```js
const job2 = {
  role: "Head of Design",
  salary: 175000,
  applicationLink: "amazon.com/careers/hod-role",
  isRemote: false,
};
```

However, as you can see, the above way of creating multiple objects is error-prone and not scalable because you can't make 100 job objects by writing that out every time you need to create a job. This is where classes come in handy.

### Creating Classes

You can create a `Job` class to simplify creating multiple jobs:

```js
class Job {
  constructor(role, salary, applicationLink, isRemote) {
    this.role = role;
    this.salary = salary;
    this.applicationLink = applicationLink;
    this.isRemote = isRemote;
  }
}
```

The code above creates a `Job` class with `role`, `salary`, `applicationLink`, and `isRemote` properties. Now you can create different jobs using the `new` keyword:

```js
let job1 = new Job(
  "Software Engineer",
  200000,
  "meta.com/careers/SWE-role/apply",
  true
);

let job2 = new Job(
  "Head of Design",
  175000,
  "amazon.com/careers/hod-role",
  false
);
```

The code above creates two different jobs with all the required fields. Let's see if this works by printing both jobs out in the console:

```js
console.log(job1);
console.log(job2);
```

The above code should print out both jobs in the console:

![job1 and job2 in the console](https://www.honeybadger.io/images/blog/posts/javascript-oop/job1_and_job2_in_the_console.png)

The image shows both jobs and all their properties in the console.

### The `this` Keyword

The [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) keyword is considered the most confusing keyword in JavaScript because it means different things depending on where it appears in your code.

In the above example, the `this` keyword refers to any object created with the `Job` class. Therefore, inside the `constructor` method, `this.role = role;` means "define the `role` property of this object you're creating as any value given to the constructor".

Also, note that the initial values you give must be in order when creating a new Job object. For example, you create a `job3` object like so:

```js
let job3 = new Job(
  "netflix.com/careers/HOE-role",
  true,
  "Head of Engineering"
);

console.log(job3)
```

The code above creates a new `job3` object with the wrong order of the properties and is missing the `isRemote` property. You would get something like this in the console:

![incomplete properties](https://www.honeybadger.io/images/blog/posts/javascript-oop/incomplete_properties.png)

The image above shows what the `job3` object will look like when printed out in the console. Notice that the `isRemote` is `undefined`.

In the next section, let's look at how to add methods to classes.

### Class Methods

When creating classes, you can add as many properties as you like. For example, if you have a `Vehicle` class, aside from basic properties like `type`, `color`, `brand`, and `year`, you probably also want to have methods like `start`, `stop`, `pickUpPassengers`, and `dropOffPassengers`.

To add methods inside classes, you can add them after the `constructor` function:

```js
class Vehicle {
  constructor(type, color, brand, year) {
    this.type = type;
    this.color = color;
    this.brand = brand;
    this.year = year;
  }
  start() {
    return "Vroom! Vroom!! Vehicle started";
  }
  stop() {
    return "Vehicle stopped";
  }
  pickUpPassengers() {
    return "Passengers picked up";
  }
  dropOffPassengers() {
    return "Passengers dropped off";
  }
}
```

The code above defines a Vehicle class with `type`, `color`, `brand`, and `year` properties, as well as `start`, `stop`, `pickUpPassengers`, and `dropOffPassengers` methods.

To run methods on the object, you can use the dot notation:

```js
const vehicle1 = new Vehicle("car", "red", "Ford", 2015);
const vehicle2 = new Vehicle("motorbike", "blue", "Honda", 2018);

console.log(vehicle1.start()); // returns 'Vroom! Vroom!! Vehicle started'
console.log(vehicle2.pickUpPassengers()); // returns "Passengers picked up"
```

The code above creates two types of vehicles using the `Vehicle` class and runs the class methods on them.

Let's look at computed properties in the next section.

## Computed Properties

Programming largely depends on changing values, so similar to how you don't want to hardcode most of the values of the class properties, you might have some dynamic property names that vary based on some values. You can use computed properties to do that; let's see how.

For example, when creating a job listing API, you might want developers to be able to change the `applyThrough` function name to another word, such as `applyThroughLinkedIn` or `applyThroughIndeed`, depending on their platform. To use computed properties, you need to wrap the property name in square brackets:

```js
let applyThrough = "applyThroughIndeed";

class Job {
  constructor(role, salary, applicationLink, isRemote) {
    this.role = role;
    this.salary = salary;
    this.applicationLink = applicationLink;
    this.isRemote = isRemote;
  }
  [applyThrough]() {
    if (applyThrough === "applyThroughLinkedin") {
      return `Apply through Linkedin`;
    } else if (applyThrough === "applyThroughIndeed") {
      return `Apply through Indeed`;
    }
  }
}
```

The code above declares the `applyThrough` variable with the string value of `"applyThroughIndeed"` and a computed method `[applyThrough]` that can be called `job1.applyThroughIndeed()`.

Computed properties make it easier for other developers to customize their code.

## Getters and Setters

When writing code in a team, you want to limit who can change certain parts of the codebase to avoid bugs. It is advisable that in OOP, certain variables and properties should be hidden when necessary.

Next, let's learn how the `get` and `set` keywords work.

### Getters

When building apps that are keen on ensuring users' privacy, for example, legal issues management apps, you want to control who can access users' data like names, emails, and addresses. The `get` keyword helps you achieve this. You can limit who can access information:

```js
class Client{
  constructor(name, age) {
    this._name = name;
    this._age = age;
  }
  get name() {
    if (userType === "Lawyer") {
      return this._name;
    } else {
      return "You are not authorized to view this information";
    }
  }
}
```

The code above declares a Client class with properties `name` and `age` and a getter that only returns the name if the user is a Lawyer. If you try to access the name as an Assistant, you'll get an error:

```js
let userType = "Assistant";
const person = new Client("Benjamin Adeleye", 24);
console.log(person.name); // returns 'You are not authorized to view this information'
```

> Note: The `this.name` is changed to `this._name` to avoid naming collisions.

Let's learn about setters next.

### Setters

The `set` keyword is the opposite of the `get` keyword. The `get` keyword is used to control who can access properties, while the `set` keyword controls who can change the value of properties.

To learn how the `set` keyword works, let's extend the previous example by adding a setter:

```js
  set name(newName) {
    if (userType === "Lawyer" && verifiedData === true) {
      this._name = newName;
    } else {
      console.log("You are not authorized to change this information");
    }
  }
```

The above code declares a `set` method that allows changes to the name only if the user is a Lawyer and documents have been verified:

```js
let userType = "Lawyer";
let verifiedData = false;
let client = new Client("Benjamin Adeleye", 30);
client.name = "Adeleye Benjamin";
console.log(client.name); // returns 'You are not authorized to change this information'
```

> Note: Methods prefixed with the `get` and `set` methods are called getters and setter functions, respectively.

Next, let's take a look at static properties and methods.

## Static Values

Sometimes you want to create properties and methods bound to the classes and not the class' instances. For example, you might want a property that counts the number of clients in the database, but you don't want that value bound to the class instances.

Let's look at static properties next.

### Static Properties

To track the number of clients in the database, you can use the `static` keyword:

```js
static clientCount = 0;
```

The code above declares a static `clientCount` property with the value of `0`. You can access static properties like this:

```js
let cl = new Client("Benjamin Adeleye", 30);
console.log(Client.clientCount); // returns 0
```

> Note: Trying to access static properties using  `console.log(cl.clientCount);` would return undefined because static properties are bound to the class and not instances.

Next, let's look at static methods.

### Static Methods

Creating static methods is very similar to creating static properties because you only need to prefix the method name with the `static` keyword:

```js
  static increaseClientCount() {
    this.clientCount++;
  }
```

The code above declares a static `increaseClientCount` method that increases the `clientCount` every time it is called.

Static methods and properties make it easy to create utility and helper functions that can be used directly on the class and not the instances.

## Private values

The [ECMAScript2022](https://2ality.com/2022/06/ecmascript-2022.html#what%E2%80%99s-new-in-ecmascript-2022) update shipped with support for private values inside JavaScript classes.

[Private fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields) and methods take encapsulation in classes to the next level because you can now create properties and methods that can only be used inside the class declaration curly braces, and any code outside those curly braces won't be able to access them.

Let's look at private properties next.

### Private Properties

You can declare private properties inside a class by prefixing the variable with the `#` sign. Let's improve the `Client` class in the sections by adding a unique id for every client:

```js
class Client {
  #client_unique_id = "";
  constructor(name, age, id) {
    this._name = name;
    this._age = age;
    this.#client_unique_id = id;
  // same as Client class...
  }
```

The code above declared a private `#client_unique_id` variable that can only be used and accessed inside the class declaration. Trying to access it outside the class will return an error:

```js
let cl = new Client("Benjamin Adeleye", 30, "##34505833404494");
console.log(cl.name);
console.log(cl.#client_unique_id); // returns Uncaught SyntaxError: Private field '#client_unique_id' must be declared in an enclosing class
```

Let's look at private methods next.

## Private Methods

As mentioned earlier, private methods are only accessible inside the class declaration. To learn how private methods work, we will add a private method that fetches the client case file documents from the database:

```js
   #fetchClientDocs(id) {
    return "Fetching client with id: " + id;
  }
```

The code above can now be used inside a public function that the user will call to get the client documents. The essence of the private function is to hide all the underlying authentication and calls to the database from the user or developer that uses the code.

> Note: You can also create private static, getter, and setter functions.

Next, let's learn how to chain class methods in the next section.

## Method Chaining

As a developer, one of the things you probably enjoy doing most is implementing a feature using as little code as possible. You can do this in JavaScript by chaining methods. For example, when a user logs into your application, you want to change the status of the user to 'online', and when they log out, you change it back to 'offline':

```js
class Twita {
  constructor(username, offlineStatus) {
    this.username = username;
    this.offlineStatus = offlineStatus;
  }
  login() {
    console.log(`${this.username} is logged in`);
    return this;
  }
  setOnlineStatus() {
    // set the online status to true
    this.offlineStatus = false;
    console.log(`${this.username} is online`);
    return this;
  }
  setOfflineStatus() {
    // set the offline status to true
    this.offlineStatus = true;
    console.log(`${this.username} is offline`);
    return this;
  }
  logout() {
    console.log(`${this.username} is logged out`);
    return this;
  }
}
```

The code above declares a `Twita` class with `username` and `offlineStatus` properties and `login`, `logout`, `setOnlineStatus`, and `setOfflineStatus` methods. To chain the methods, you can use the dot notation:

```js
const user = new Twita("Adeleye", true);
twita.login().setOnlineStatus().logout().setOfflineStatus();
```

The code above will run all the functions sequentially on the `user` object and return a response:

![Method chain result](https://www.honeybadger.io/images/blog/posts/javascript-oop/method_chain_result.png)

The image shows the result of the [method chain](https://en.wikipedia.org/wiki/Method_chaining).

> Note: You need to return the current object by returning `this` at the end of each function.

Next, let's look at how to build on existing classes through inheritance.

## Class Inheritance

When working with objects, you'll most likely run into situations where you need to create an object that is really similar to an object that already exists in your code, but you know they can't be the same. For example, when building an e-commerce application, you will have a `Product` class with properties like `name`, `price`, `description`, and `image` and methods like `formatPrice` and `addToCart`.

However, what if you have multiple types of products with different specifications:

- Books with author, weight, and page details.
- Furniture with length, width, height, and wood-type details.
- Movies CDs with size, duration, and content details.

In this case, creating a class for each product will result in a lot of code duplication, which is one of the major rules of OOP and programming in general - don't repeat yourself (DRY).

Class inheritance allows you to create objects based on other objects. For example, you can solve the problem mentioned above by creating a `Product` class:

```js
class Product {
  constructor(name, price, description, image) {
    this.name = name;
    this.price = price;
    this.description = description;
    this.image = image;
  }
  formatprice() {
    return `${this.price}$`;
  }
  addToCart() {
    cart.add(this);
  }
}
```

Then, create a subclass for each product type using the `extends` keyword:

```js
class Book extends Product {
  constructor(name, price, description, image, weight, pages, author) {
    super(name, price, description, image);
    this.author = author;
    this.weight = weight;
    this.pages = pages;
  }
}

class Movie extends Product {
  constructor(
    name,
    price,
    description,
    image,
    size,
    contentDescription,
    duration
  ) {
    super(name, price, description, image);
    this.size = size;
    this.contentDescription = contentDescription;
    this.duration = duration;
  }
}

class Furniture extends Product {
  constructor(
    name,
    price,
    description,
    image,
    woodType,
    length,
    height,
    width
  ) {
    super(name, price, description, image);
    this.woodType = woodType;
    this.length = length;
    this.height = height;
    this.width = width;
  }
}
```

The code above declares the `Book`, `Movie`, and `Furniture` product types by extending the `Product` class.

There are two new keywords in the above code: `extends` and `super`. Let's look at them in the next sections.

### The `extends` Keyword

The `extends` keyword is self-explanatory; it is used to extend the capability of another class. In our case, we used it to create the `Book`, `Movie`, and `Furniture` classes by extending the `Product` class.

### The `super` Keyword

The `super` keyword eliminates the multiple declarations you would otherwise need to repeat for each new child class. For example, the `super` function called in the above code replaced the following code:

```js
    this.name = name;
    this.price = price;
    this.description = description;
    this.image = image;
```

Remember DRY? the reason for this is not to repeat the above code because it has been written inside the `Product` class.

The `super` function can be omitted if the child class does not need a constructor:

```js
class Animal {
  constructor(name, species, color) {
    this.name = name;
    this.species = species;
    this.color = color;
  }
  makeSound() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Bird extends Animal {
  fly() {
    console.log(`${this.name} flies.`);
  }
}
```

The code above declares a parent `Animal` class and a child `Bird` class that does not need a constructor to work because it is not declaring any new variable inside the constructor. Therefore, the following code should work:

```js
const bird = new Bird('Chloe', 'Parrot', 'Green'); 
console.log(`${bird.name} is a ${bird.color} ${bird.species}`);
```

The code above will work even though there's nothing like `name`, `color`, or `species` inside the `Bird` class:

![Child class without constructor and super result](https://www.honeybadger.io/images/blog/posts/javascript-oop/child_class_without_constructor_and_super_result.png)

You don't need to call `super` or repeat the same constructor if you only add methods to the child class.

## Conclusion

In this tutorial, you learned about object-oriented programming and classes in JavaScript and how it helps to keep your code clean, DRY, and reusable. We covered the four core concepts of object-oriented programming, including abstraction, encapsulation, inheritance, and polymorphism.

You also learned that using object-oriented programming paradigms and classes in your code has a lot of advantages, from improving application structure and code reusability to reducing code complexity.

Whoa! That was a long ride. Thanks for reading.