OOPs using JavaScript
- Object
- Class
- Encapsulation
- Inheritance
- Polymorphism
- Abstraction
- Prototypal Inheritance
- Private Data
- Getters & Setters
a) Objects
Objects are collections of related data and methods. They are the building blocks of OOP. In JavaScript, objects can be created using object literals, constructors, or classes.
b) Classes
Classes in JavaScript are blueprints for creating objects. They define the properties and methods that objects will have.
Example:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
}
let person1 = new Person("Debasis", 28);
person1.greet(); // Output: Hello, my name is Debasis.
c) Encapsulation
Encapsulation refers to bundling the data (properties) and methods (functions) together and restricting direct access to some components using access modifiers like private.
Example using private fields:
class Person {
#ssn; // Private field
constructor(name, age, ssn) {
this.name = name;
this.age = age;
this.#ssn = ssn;
}
getDetails() {
return `Name: ${this.name}, Age: ${this.age}`;
}
// Private field is not directly accessible outside the class
}
let person1 = new Person("Debasis", 28, "123–45–6789");
console.log(person1.getDetails()); // Output: Name: Debasis, Age: 28
console.log(person1.#ssn); // Error: Private field '#ssn' must be declared in an enclosing classd) Inheritance
Inheritance allows a class (child class) to derive properties and methods from another class (parent class), enabling code reuse.
Example:
class Animal {
constructor(name) {
this.name = name;
}
makeSound() {
console.log(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
makeSound() {
console.log(`${this.name} barks.`);
}
}
let dog = new Dog("Buddy");
dog.makeSound(); // Output: Buddy barks.e) Polymorphism
Polymorphism allows objects to take on many forms. For example, a method can behave differently based on the object calling it.
Example:
class Shape {
draw() {
console.log("Drawing a shape");
}
}
class Circle extends Shape {
draw() {
console.log("Drawing a circle");
}
}
class Square extends Shape {
draw() {
console.log("Drawing a square");
}
}
let shapes = [new Shape(), new Circle(), new Square()];
shapes.forEach(shape => shape.draw());
/* Output:
Drawing a shape
Drawing a circle
Drawing a square
*/f) Abstraction
Abstraction hides unnecessary details and shows only the relevant functionalities. In JavaScript, abstraction can be achieved by using interfaces (in TypeScript) or by designing classes to hide internal implementation details.
Prototypal Inheritance (Another Variance of OOPs)
JavaScript uses prototypes as its object model rather than traditional classes (prior to ES6). Every object has an internal property [[Prototype]] that links to another object. Methods and properties can be inherited via the prototype chain.
Example:
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
console.log(`Hello, my name is ${this.name}.`);
};
let person1 = new Person("Debasis");
person1.greet(); // Output: Hello, my name is Debasis.Concept: Encapsulation and Data Hiding
One of the key principles of Object-Oriented Programming (OOP) is Encapsulation, which combines data (fields/properties) and methods (functions) into a single unit, usually a class.
Get Debasis’s stories in your inbox
Join Medium for free to get updates from this writer.
Data hiding is implemented to protect sensitive data (e.g., account numbers, passwords, or IDs). This ensures that the data is not directly accessible from outside the class. It can only be accessed or modified using controlled methods like getters and setters.
How to Implement Private Data in JavaScript
In modern JavaScript (ES6 and later), you can use:
- Private Fields using the
#symbol. - Closures to simulate private properties.
- Getters and Setters to control access.
1. Private Fields using #
The # syntax makes a property private so it cannot be accessed directly outside the class.
Example: Encapsulating a sensitive “account balance” with controlled access:
class BankAccount {
#accountNumber; // Private field
#balance; // Private field
constructor(accountNumber, balance) {
this.#accountNumber = accountNumber;
this.#balance = balance; // Initial balance
}
// Getter for account number
getAccountNumber() {
return `Account Number: ****${this.#accountNumber.slice(-4)}`; // Masking for security
}
// Method to get balance (controlled access)
getBalance() {
return `Your account balance is: $${this.#balance}`;
}
// Method to deposit money
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`$${amount} deposited successfully.`);
} else {
console.log("Invalid deposit amount.");
}
}
// Method to withdraw money with validation
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`$${amount} withdrawn successfully.`);
} else {
console.log("Insufficient funds or invalid amount.");
}
}
}
// Usage
const myAccount = new BankAccount("1234567890", 1000);
console.log(myAccount.getAccountNumber()); // Output: Account Number: ****7890
console.log(myAccount.getBalance()); // Output: Your account balance is: $1000
myAccount.deposit(500); // Output: $500 deposited successfully.
console.log(myAccount.getBalance()); // Output: Your account balance is: $1500
myAccount.withdraw(200); // Output: $200 withdrawn successfully.
console.log(myAccount.getBalance()); // Output: Your account balance is: $1300
// Attempting to access private fields directly
console.log(myAccount.#balance); // Error: Private field '#balance' must be declared in an enclosing classKey Points in the Example
- Private Fields (
#):
#accountNumberand#balanceare private fields, meaning they cannot be accessed directly outside the class.- Attempting to do so throws an error.
2. Controlled Access:
- Methods like
getAccountNumber()andgetBalance()provide controlled and secure access to sensitive data.
3. Encapsulation:
BankAccountencapsulates both data (fields) and methods to manage operations like deposit and withdrawal.
4. Data Hiding:
- The actual
#balanceand#accountNumberfields remain hidden from outside access, ensuring security.
2. Using Getters and Setters
JavaScript provides getters and setters to access or modify private data in a controlled way.
Example: Using getters and setters for sensitive data:
class User {
#password;
constructor(username, password) {
this.username = username;
this.#password = password;
}
// Getter for masked password
get password() {
return "******"; // Masking password
}
// Setter to allow password update with validation
set password(newPassword) {
if (newPassword.length >= 6) {
this.#password = newPassword;
console.log("Password updated successfully.");
} else {
console.log("Password must be at least 6 characters long.");
}
}
// Method to validate password (example)
validatePassword(inputPassword) {
return this.#password === inputPassword ? "Password matched" : "Incorrect password";
}
}
//Try these in an editor
const user = new User("Debasis", "secret123");
console.log(user.password); // Output: ******
user.password = "newpass"; // Output: Password must be at least 6 characters long
user.password = "newpass12"; // Output: Password updated successfully.
console.log(user.validatePassword("newpass12")); // Output: Password matched
console.log(user.validatePassword("wrongpass")); // Output: Incorrect passwordAdvantages of Using Private Fields and Getters/Setters
- Data Security:
- Sensitive data (like
#passwordor#balance) is not exposed directly. - Controlled methods ensure proper access.
2. Validation:
- You can enforce rules (e.g., password length, valid deposit amounts) when setting or modifying data.
3. Abstraction:
- Internal implementation details are hidden, and the user interacts only with methods or getters/setters.
4. Maintainability:
- If the internal implementation changes, it won’t affect code that depends on these methods.
3. Using Closures for Data Hiding
Before private fields (#), closures were used to simulate private data.
Example:
function createBankAccount(accountNumber, initialBalance) {
let balance = initialBalance; // Private variable
return {
getAccountNumber: () => `****${accountNumber.slice(-4)}`,
getBalance: () => `Balance: $${balance}`,
deposit: (amount) => {
if (amount > 0) balance += amount;
},
withdraw: (amount) => {
if (amount > 0 && amount <= balance) balance -= amount;
},
};
}
const account = createBankAccount("1234567890", 1000);
console.log(account.getAccountNumber()); // Output: ****7890
console.log(account.getBalance()); // Output: Balance: $1000
account.deposit(500);
console.log(account.getBalance()); // Output: Balance: $1500Here:
balanceis private and not accessible outside the function scope.- All methods (
getBalance,deposit) act as closures and control access.


