Object-Oriented Programming in JavaScript
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!