Sitemap
Debasis

Debasis

OOPs using JavaScript

5 min readDec 17, 2024

--

Press enter or click to view image in full size
  1. Object
  2. Class
  3. Encapsulation
  4. Inheritance
  5. Polymorphism
  6. Abstraction
  7. Prototypal Inheritance
  8. Private Data
  9. 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 class

d) 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:

  1. Private Fields using the # symbol.
  2. Closures to simulate private properties.
  3. 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 class

Key Points in the Example

  1. Private Fields (#):
  • #accountNumber and #balance are private fields, meaning they cannot be accessed directly outside the class.
  • Attempting to do so throws an error.

2. Controlled Access:

  • Methods like getAccountNumber() and getBalance() provide controlled and secure access to sensitive data.

3. Encapsulation:

  • BankAccount encapsulates both data (fields) and methods to manage operations like deposit and withdrawal.

4. Data Hiding:

  • The actual #balance and #accountNumber fields 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 password

Advantages of Using Private Fields and Getters/Setters

  1. Data Security:
  • Sensitive data (like #password or #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: $1500

Here:

  • balance is private and not accessible outside the function scope.
  • All methods (getBalance, deposit) act as closures and control access.

--

--