Object-Oriented Programming in JavaScript

ยท

5 min read

Introduction

Object-Oriented Programming (OOP) is a programming paradigm based on the concept of "objects". These objects can contain data, in the form of fields (often known as properties or attributes), and code, in the form of procedures (often known as methods). JavaScript is a versatile language that supports OOP principles, allowing developers to create more modular, reusable, and maintainable code.

Functions Inside Objects

Functions inside objects, also known as methods, operate on the object's data. This is a basic principle of OOP, encapsulating data and behavior together.

Example:

const user = {
  name: "Divy",
  age: 25,
  getBirthYear: function () {
    return new Date().getFullYear() - this.age;
  },
};

console.log(user.getBirthYear());

Explanation:

In this example, getBirthYear is a method defined within the user object. It calculates the birth year of the user based on the current year and the age property of the object.

Factory Functions

Factory functions are a way to create objects in JavaScript without using the new keyword. They allow the creation of multiple instances of objects with similar properties and methods.

Example:

function createUser(firstName, lastName, age) {
  const user = {
    firstName,
    lastName,
    age,
    getBirthYear() {
      return new Date().getFullYear() - this.age;
    },
  };
  return user;
}

const user1 = createUser("Divy", "Parekh", 21);
const user2 = createUser("D", "P", 26);
console.log(user1.getBirthYear());
console.log(user2.getBirthYear());
console.log(user1.getBirthYear === user2.getBirthYear);

Explanation:

Here, createUser is a factory function that returns a new user object each time it is called. Although each user has a getBirthYear method, the function is redefined for each object, which can lead to higher memory usage.

Constructor Functions

Constructor functions provide a more efficient way to create objects, sharing methods across instances using prototypes.

Example:

function CreateUser(firstName, lastName, age) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.age = age;
}

CreateUser.prototype.getBirthYear = function () {
  return new Date().getFullYear() - this.age;
};

const user1 = new CreateUser("Divy", "Parekh", 21);
const user2 = new CreateUser("D", "P", 26);

console.log(user1.getBirthYear());
console.log(user2.getBirthYear());
console.log(user2.__proto__.getBirthYear === user1.__proto__.getBirthYear);

Explanation:

Using CreateUser as a constructor function, the getBirthYear method is defined on CreateUser.prototype, ensuring that all instances share the same method, thereby saving memory.

Classes

ES6 introduced classes, which provide a clearer and more concise syntax for creating constructor functions and working with prototypes.

Example:

class CreateUser {
  constructor(firstName, lastName, age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  getBirthYear() {
    return new Date().getFullYear() - this.age;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const user1 = new CreateUser("Divy", "Parekh", 21);
const user2 = new CreateUser("D", "P", 26);

console.log(user1.getFullName());

Explanation:

The CreateUser class encapsulates the properties and methods related to a user. Methods defined within a class are automatically added to the prototype of the created objects.

Private and Public Properties in Classes

JavaScript classes support private and public properties and methods, enhancing encapsulation. Private properties and methods are denoted by a # prefix.

Example:

class CreateUser {
  #age;
  constructor(firstName, lastName, age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.#age = age;
  }

  #getFirstName() {
    return `${this.firstName}`;
  }

  getBirthYear() {
    return new Date().getFullYear() - this.#age;
  }

  getFullName() {
    return `${this.#getFirstName()} ${this.lastName}`;
  }
}

const user1 = new CreateUser("Divy", "Parekh", 21);
const user2 = new CreateUser("D", "P", 26);

console.log(user1.getFullName());
console.log(user1.getBirthYear());

Explanation:

In this example, #age and #getFirstName are private properties and methods, meaning they are only accessible within the class itself, not from outside instances.

Static Properties and Methods

Static properties and methods belong to the class itself rather than any instance of the class. They are often used for utility functions that are related to the class but do not operate on instance data.

Example:

class CreateUser {
  constructor(firstName, lastName, age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  static greet = "hello";

  getBirthYear() {
    return new Date().getFullYear() - this.age;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  static {
    console.log("inside static block");
    this.demo = "demo";
    this.demo = function () {
      return "Demo";
    };
  }
}

const user1 = new CreateUser("Divy", "Parekh", 21);
const user2 = new CreateUser("D", "P", 26);

console.log(user1.greet); // undefined
console.log(CreateUser.greet); // "hello"

Explanation:

Static properties and methods, such as greet, are accessed on the class itself, not on instances of the class. The static block is used to initialize static properties.

Getters and Setters

Getters and setters allow defining methods to get and set the value of an object property, providing a way to control access to the properties.

Example:

const user = {
  firstName: "Divy",
  lastName: "Parekh",
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  set fullName(value) {
    let val = value.split(" ");
    this.firstName = val[0];
    this.lastName = val[1];
  },
};
console.log(user.fullName);
user.fullName = "D P";
console.log(user.fullName);

Getters and Setters in Classes

class User {
  constructor(firstName, lastName, age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  set fullName(value) {
    let val = value.split(" ");
    this.firstName = val[0];
    this.lastName = val[1];
  }
}

const user1 = new User("Divy", "Parekh", 21);
console.log(user1.fullName);
user1.fullName = "D P";
console.log(user1.fullName);

Explanation:

Getters and setters in JavaScript classes work similarly to those in objects, providing controlled access to object properties.

Prototypal Inheritance, Extends, and Super Keyword

Prototypal inheritance allows objects to inherit properties and methods from other objects. The extends keyword is used in classes to create a subclass, and super is used to call the constructor of the parent class.

Example:

class User {
  constructor(firstName, lastName, age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  getBirthYear() {
    return new Date().getFullYear() - this.age;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  set fullName(value) {
    let val = value.split(" ");
    this.firstName = val[0];
    this.lastName = val[1];
  }
}

class Student extends User {
  constructor(firstName, lastName, age, standard) {
    super(firstName, lastName, age);
    this.standard = standard;
  }

  study() {
    console.log("studying ...");
  }
}

class Employee extends User {
  constructor(firstName, lastName, age, company) {
    super(firstName, lastName, age);
    this.company = company;
  }

  companyName() {
    return `${this.company}`;
  }
}

const user1 = new User("Divy", "Parekh", 21);
const student1 = new Student("D", "P", 21, "12th");
const employee1 = new Employee("D", "P", 21, "Groww");

console.log(user1);
console.log(student1);
console.log(employee1);
console.log(employee1.companyName());

Explanation:

In this example, Student and Employee are subclasses of User. They inherit properties and methods from User but can also define their own. The super keyword is used to call the parent class's constructor.

If you have any questions or suggestions then, feel free to reach out to me on Twitter or LinkedIn. You can find me on Twitter DivyParekh and LinkedIn at LinkedIn. I look forward to connecting with you and discussing all things!

Did you find this article valuable?

Support Divy Parekh's blog by becoming a sponsor. Any amount is appreciated!

ย