🔥Connect: https://www.subham.online
🔥Twitter: https://twitter.com/TheSubhamMaity
▶️ Intro
⭐ What is OOPs ?
Object-oriented programming is an approach to solving problems by creating objects
⭐ 4 Pillars of OOP
Terminologies in OOP
Abstraction - Hiding internal details (show only essential info!)
Encapsulation - The act of putting various components together (in a capsule)
Inheritance - The act of deriving new things from existing things
Polymorphism - One entity, many forms
▶️ Prototypes and proto
JavaScript's objects have a special property called prototype
that is either null
or references another object
When we try to read a property from an object, and it's missing, JavaScript automatically takes it from the prototype. This is called prototype inheritance
⭐ Setting a Prototype
We can set the prototype by setting __proto__
. If we read a property from an object that is not in the object but is present in the prototype, JavaScript will take it from the prototype. If we have a method in the object, it will be called from the object. If it's missing in the object and present in the prototype, it will be called from the prototype.
⭐ Example:
//It will work properly
let p = {
run : () => {
console.log("run")
}
}
p.run()//Output: - run
//Let's define another property
let a = {
name : " subham"
}
a.run() //TypeError: a.run is not a function
//Now with proto
let b = {
name : " subham"
}
b.__proto__ = p
b.run() //Output: - run
Simply, you can inherit some object’s prototype in another object. This is called prototype inheritance.
//It will work properly
let p = {
run : () => {
console.log("p run")
}
}
p.run()//Output: - p run
//Now with proto
let b = {
run : () => {
console.log("b run")
}
}
b.__proto__ = p
b.run() //Output: - b run
If a property or method is already present in the object, JavaScript will use that property or method. If it's not present in the object but is present in the prototype, JavaScript will take it from the prototype. In this example, since the run
method is already present in the b
object, it will print 'b run'.
▶️ Classes and Object
In object-oriented programming, a class is a template definition of the methods and variables in a particular kind of object
In object-oriented programming, an object is a specific instance of a class (or struct) which has been allocated in memory.
⭐ Example:
//class
class GoogleForm {
submit() {
console.log(this.name + " " + this.roll + " Your form submitted")
}
cancel() {
console.log(this.name + " " + this.roll +" Your form cancelled")
}
fill(given_name , roll) {
this.name = given_name
this.roll = roll
}
}
//object
const student1Form = new GoogleForm()
student1Form.fill("Rahul" , 24)
const student2Form = new GoogleForm()
student2Form.fill("Raj" , 25)
student2Form.cancel()
student1Form.submit()
student2Form.submit()
▶️ Constructor
In JavaScript, a constructor is a special function that creates and initializes objects, setting their initial state and properties.
Let's say they forget to fill the form and click on the submit button it will throw undefined!
class Form {
submit() {
console.log(this.name + ": Your form is submitted for train number: " + this.trainno)
}
cancel() {
console.log(this.name + ": This form is cancelled for train number: " + this.trainno)
this.trainno = 0
}
fill(givenname, trainno) {
this.name = givenname
this.trainno = trainno
}
}
let myForm1 = new Form()
let myForm2 = new Form()
//
// myForm1.fill("Gaurav", 1234)
//
// myForm2.fill("Rahul", 5678)
myForm1.submit()
myForm2.submit()
myForm2.cancel()
// Output: undefined: Your form is submitted for train number: undefined
// Output: undefined: Your form is submitted for train number: undefined
// Output: undefined: This form is cancelled for train number: undefined
Now create constructor,
class Form {
constructor() {
this.name = "Gaurav"
this.trainno = 0
}
submit() {
console.log(this.name + ": Your form is submitted for train number: " + this.trainno)
}
cancel() {
console.log(this.name + ": This form is cancelled for train number: " + this.trainno)
this.trainno = 0
}
fill(givenname, trainno) {
this.name = givenname
this.trainno = trainno
}
}
let myForm1 = new Form()
let myForm2 = new Form()
// myForm1.fill("Gaurav", 1234)
//
// myForm2.fill("Rahul", 5678)
myForm1.submit()
myForm2.submit()
myForm2.cancel()
// Output: Gaurav: Your form is submitted for train number: 0
// Output: Gaurav: Your form is submitted for train number: 0
// Output: Gaurav: This form is cancelled for train number: 0
⭐ Types of Constructors
- Non-Parameterized Constructor: A constructor that has no arguments.
class Example {
constructor() {
this.property = "default value";
}
}
- Parameterized Constructor: A constructor that takes parameters.
class Example {
constructor(value) {
this.property = value;
}
}
- Copy Constructor: JavaScript does not have a built-in copy constructor like C++ or Java. However, you can create a method to copy an object.
class Example {
constructor(value) {
this.property = value;
}
copy() {
return new Example(this.property);
}
}
const original = new Example("original value");
const copy = original.copy();
Unlike languages like C++, JavaScript does not have destructors. Instead, JavaScript relies on an efficient garbage collector that automatically deallocates memory.
▶️ Inheritance
The capability of a class to derive properties and characteristics from another class is called Inheritance.
⭐ Why ?
If you don't know what is Inheritance
class Animal {
constructor(name, color , age) {
this.name = name
this.color = color
this.age = age
}
run() {
console.log(this.name + ' is running')
}
shout() {
console.log(this.name + ' is shouting')
}
sleep() {
console.log(this.name + ' is sleeping')
}
}
//If you are nub developer you will do
class Monkey {
constructor(name, color) {
this.name = name
this.color = color
}
run() {
console.log(this.name + ' is running')
}
shout() {
console.log(this.name + ' is shouting')
}
sleep() {
console.log(this.name + ' is sleeping')
}
eatBanana() {
console.log(this.name + ' is eating banana')
}
}
const animal_1 = new Monkey('Simba monkey', 'Brown', 2)
const animal_2 = new Animal('Donkey', 'White', 3)
animal_1.eatBanana()
animal_2.shout()
If you know
//Parent Class - Base Class
class Animal {
constructor(name, color , age) {
this.name = name
this.color = color
this.age = age
}
run() {
console.log(this.name + ' is running')
}
shout() {
console.log(this.name + ' is shouting')
}
sleep() {
console.log(this.name + ' is sleeping')
}
}
//Child Class - Derived Class
class Monkey extends Animal{
eatBanana() {
console.log(this.name + ' is eating banana')
}
//you can also add new methods
hide() {
console.log(this.name + ' is hiding')
}
}
const animal_1 = new Monkey('Simba monkey', 'Brown', 2)
const animal_2 = new Animal('Donkey', 'White', 3)
animal_1.eatBanana()
animal_1.run()
animal_1.hide()
animal_2.shout()
⭐ Types of Inheritance
- Single Inheritance when one class inherits another class, it is known as single level inheritance.
class Shape {
area() {
console.log("Displays Area of Shape");
}
}
class Triangle extends Shape {
area(h, b) {
console.log((1/2) * b * h);
}
}
const triangle = new Triangle();
triangle.area(10, 5); // Output: 25
- Hierarchical Inheritance is defined as the process of deriving more than one class from a base class.
class Shape {
area() {
console.log("Displays Area of Shape");
}
}
class Triangle extends Shape {
area(h, b) {
console.log((1/2) * b * h);
}
}
class Circle extends Shape {
area(r) {
console.log(3.14 * r * r);
}
}
const triangle = new Triangle();
triangle.area(10, 5); // Output: 25
const circle = new Circle();
circle.area(7); // Output: 153.86
- Multilevel Inheritance is a process of deriving a class from another derived class.
class Shape {
area() {
console.log("Displays Area of Shape");
}
}
class Triangle extends Shape {
area(h, b) {
console.log((1/2) * b * h);
}
}
class EquilateralTriangle extends Triangle {
constructor(side) {
super();
this.side = side;
}
area() {
console.log((Math.sqrt(3) / 4) * this.side * this.side);
}
}
const equilateralTriangle = new EquilateralTriangle(5);
equilateralTriangle.area(); // Output: 10.825317547305486
- Hybrid Inheritance is a combination of simple, multiple inheritance and hierarchical inheritance. JavaScript does not support multiple inheritance directly, but we can achieve similar behavior using mixins.
class Shape {
area() {
console.log("Displays Area of Shape");
}
}
class Triangle extends Shape {
area(h, b) {
console.log((1/2) * b * h);
}
}
class Circle extends Shape {
area(r) {
console.log(3.14 * r * r);
}
}
const mixin = (Base) => class extends Base {
perimeter() {
console.log("Calculates Perimeter");
}
};
class EquilateralTriangle extends mixin(Triangle) {
constructor(side) {
super();
this.side = side;
}
area() {
console.log((Math.sqrt(3) / 4) * this.side * this.side);
}
}
const equilateralTriangle = new EquilateralTriangle(5);
equilateralTriangle.area(); // Output: 10.825317547305486
equilateralTriangle.perimeter(); // Output: Calculates Perimeter
▶️ Method Overriding
If the same method is defined in both the superclass and the subclass, then the method of the subclass class overrides the method of the superclass
- General
class human {
constructor(name , age , body_type) {
this.name = name
this.age = age
this.body_type = body_type
}
getName() {
console.log("The name of the human is : ", this.name)
}
getAge() {
console.log("The age of the human is :", this.age)
}
getBodyType() {
console.log("The body type of the human is :", this.body_type)
}
}
class student extends human {}
const student_1 = new student("Subham" , 24 , "Thin")
student_1.getAge() //The age of the human is : 24
⭐ Super Keyword - Types
The super keyword is used to call the parent class's constructor to access its properties and methods.
Overriding the Constructor
class Human {
constructor(name, age, bodyType) {
this.name = name;
this.age = age;
this.bodyType = bodyType;
}
getName() {
console.log("The name of the human is:", this.name);
}
getAge() {
console.log("The age of the human is:", this.age);
}
getBodyType() {
console.log("The body type of the human is:", this.bodyType);
}
}
class Student extends Human {
constructor() {
super("Rahul", 80, "Fat");
}
}
const student1 = new Student();
student1.getName(); // The name of the human is: Rahul
Overriding a Method
class Human {
constructor(name, age, bodyType) {
this.name = name;
this.age = age;
this.bodyType = bodyType;
}
getName() {
console.log("The name of the human is:", this.name);
}
getAge() {
console.log("The age of the human is:", this.age);
}
getBodyType() {
console.log("The body type of the human is:", this.bodyType);
}
}
class Student extends Human {
constructor() {
super("Rahul", 80, "Fat");
}
// Overriding using super keyword in child class
getAge() {
super.getAge();
console.log("The age of the student is:", 20);
}
}
const student1 = new Student();
student1.getAge(); // The age of the human is: 80
// The age of the student is: 20
⭐ Key Points of Method Overriding
Same Method Name: The method in the child class must have the same name as in the parent class.
Same Parameters: The method in the child class must have the same parameter list as the parent class method.
IS-A Relationship: Method overriding only occurs in two classes that have an IS-A relationship (inheritance).
Access Modifiers: The overriding method can have a less restrictive access modifier, but not a more restrictive one.
Super Keyword: You can use the
super
keyword to call the overridden method from the parent class.
⭐ Extra Notes
Note 1
class human {
constructor() {
console.log("Human class constructor")
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
}
const student_1 = new student()
student_1.eat()
//Human class constructor
// Human can eat
If you don’t explicitly define a constructor in a subclass, JavaScript will automatically create one for you that calls the parent class’s constructor using super(). like this
class human {
constructor() {
console.log("Human class constructor")
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
constructor(...arg) {
super(...arg);
}
}
const student_1 = new student()
student_1.eat()
Note 2
class human {
constructor() {
console.log("Human class constructor")
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
constructor() {
console.log("This is student class constructor")
}
}
const student_1 = new student()
student_1.eat()
// console.log("This is student class constructor")
//ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
You have to use super keyword like this
class human {
constructor() {
console.log("Human class constructor")
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
constructor() {
super()
console.log("This is student class constructor")
}
}
const student_1 = new student()
student_1.eat()
Note 3
class human {
constructor(name) {
console.log("Human class constructor" , name)
this.name = name
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
constructor(name) {
this.name = name //not allow
super()
console.log("Student class constructor" , name)
}
}
const student_1 = new student("subham")
student_1.eat()
// this.name = name
// ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
After super keyword you can use this
class human {
constructor(name) {
console.log("Human class constructor" , name)
this.name = name
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
constructor(name) {
super()
this.name = name
console.log("Student class constructor" , name)
}
}
const student_1 = new student("subham")
student_1.eat()
// Human class constructor undefined
// Student class constructor subham
// Human can eat
▶️ Method Overloading
Having two or more methods (or functions) in a class with the same name and different arguments (or parameters)
⭐ Can We Overload a Function in JavaScript?
In JavaScript, method overloading as seen in some other languages (like Java) is not natively supported. This means you cannot define multiple methods with the same name but different parameters in the same class. However, you can achieve similar functionality using techniques like checking the number and type of arguments within a single method.
You can't do this in JS
class Calculator {
add(a, b) {
return a + b;
}
add(a, b, c) {
return a + b + c;
}
}
const calc = new Calculator();
console.log(calc.add(1, 2)); // This will throw an error because the first add method is overwritten
If you want, you can achieve by doing this
class Calculator {
add(...args) {
if (args.length === 2) {
return args[0] + args[1];
} else if (args.length === 3) {
return args[0] + args[1] + args[2];
} else {
throw new Error("Invalid number of arguments");
}
}
}
const calc = new Calculator();
console.log(calc.add(1, 2)); // Output: 3
console.log(calc.add(1, 2, 3)); // Output: 6
▶️ Access Modifiers
Access modifier is a keyword that is used to set the accessibility of a class member
⭐ Types of Access Modifiers
Public: Members declared as public are accessible from any other class.
Protected: Members declared as protected are accessible within the same class and by derived class instances.
Private: Members declared as private are accessible only within the same class.
⭐ Accessibility Table
Modifier | Parent Class | Child Class | Outside Class |
Public | ✔️ | ✔️ | ✔️ |
Protected | ✔️ | ✔️ | ❌ |
Private | ✔️ | ❌ | ❌ |
⭐ Example
1. Public Members
Public members are accessible from anywhere.
class Parent {
publicProperty = "I'm public";
publicMethod() {
return "This is a public method";
}
}
class Child extends Parent {
useParentPublic() {
console.log(this.publicProperty);
console.log(this.publicMethod());
}
}
const parent = new Parent();
const child = new Child();
console.log(parent.publicProperty); // Output: I'm public
console.log(parent.publicMethod()); // Output: This is a public method
child.useParentPublic();
// Output:
// I'm public
// This is a public method
In this example, publicProperty
and publicMethod
are accessible from:
Within the Parent class
Within the Child class
Outside any class
2. Protected Members (Simulated)
In JavaScript, we use an underscore prefix to indicate protected members by convention. They're still technically public, but developers agree not to access them directly outside the class or its subclasses.
class Parent {
_protectedProperty = "I'm protected";
_protectedMethod() {
return "This is a protected method";
}
}
class Child extends Parent {
useParentProtected() {
console.log(this._protectedProperty);
console.log(this._protectedMethod());
}
}
const parent = new Parent();
const child = new Child();
child.useParentProtected();
// Output:
// I'm protected
// This is a protected method
// These work, but violate the convention:
console.log(parent._protectedProperty);
console.log(parent._protectedMethod());
In this scenario:
_protectedProperty
and_protectedMethod
are accessible within ParentThey're also accessible within Child (inheritance)
They're technically accessible outside, but this violates the convention
3. Private Members
Private members are truly private and only accessible within the class they're defined in.
class Parent {
#privateProperty = "I'm private";
#privateMethod() {
return "This is a private method";
}
usePrivate() {
console.log(this.#privateProperty);
console.log(this.#privateMethod());
}
}
class Child extends Parent {
tryToUseParentPrivate() {
// These would cause errors if uncommented:
// console.log(this.#privateProperty);
// console.log(this.#privateMethod());
}
}
const parent = new Parent();
const child = new Child();
parent.usePrivate();
// Output:
// I'm private
// This is a private method
// These would cause errors:
// console.log(parent.#privateProperty);
// console.log(parent.#privateMethod());
// child.tryToUseParentPrivate();
In this case:
#privateProperty
and#privateMethod
are only accessible within ParentThey're not accessible in Child, even though it extends Parent
They're not accessible outside the class at all
Key Takeaways
Public members (default) are accessible everywhere.
Protected members (convention with
_
) are accessible within the class and subclasses, but shouldn't be accessed outside (though technically they can be).Private members (with
#
) are only accessible within the defining class, not in subclasses or outside.When using protected members, they behave like public members in terms of accessibility, but developers agree to treat them as if they were protected.
True privacy and encapsulation are only achieved with private members using the
#
syntax.
▶️ Static
The static keyword defines a static method or field for a class
A static method is a method that belongs to the class itself, not to any specific instance of the class.
class Animal {
constructor(name) {
this.name = Animal.capitalize(name);
}
static capitalize(name) {
return name.charAt(0).toUpperCase() + name.slice(1);
}
walk() {
console.log(`Animal ${this.name} is walking`);
}
}
const animal = new Animal("lion");
animal.walk(); // Output: Animal Lion is walking
console.log(Animal.capitalize("elephant")); // Output: Elephant
Key points:
The
capitalize
method is declared as static using thestatic
keyword.It's called on the class (
Animal.capitalize
) not on instances.It can be used inside the constructor or other methods using the class name.
⭐ Inheritance and Static Methods
Static methods are inherited by subclasses:
class Human extends Animal {
static greet() {
console.log("Hello!");
}
}
const human = new Human("john");
human.walk(); // Output: Animal John is walking
console.log(Human.capitalize("sarah")); // Output: Sarah
Human.greet(); // Output: Hello!
Note:
The
Human
class inherits the staticcapitalize
method fromAnimal
.Human
can also define its own static methods, likegreet
.
⭐ Calling Static Methods from Non-Static Methods
You can call static methods from non-static methods, but you need to use the class name:
class Calculator {
static add(a, b) {
return a + b;
}
multiply(a, b) {
// Using a static method in a non-static method
return Calculator.add(a, 0) * b;
}
}
const calc = new Calculator();
console.log(calc.multiply(3, 4)); // Output: 12
console.log(Calculator.add(5, 6)); // Output: 11
⭐ Static Methods vs. Instance Methods
Here's a comparison to illustrate the difference:
class MyClass {
static staticMethod() {
return "I'm a static method";
}
instanceMethod() {
return "I'm an instance method";
}
}
console.log(MyClass.staticMethod()); // Output: I'm a static method
const obj = new MyClass();
console.log(obj.instanceMethod()); // Output: I'm an instance method
// This would throw an error:
// console.log(MyClass.instanceMethod());
// This would also throw an error:
// console.log(obj.staticMethod());
⭐ Use Cases for Static Methods
Utility Functions: Methods that don't require object state.
Factory Methods: Creating instances with special properties.
Cache or Fixed-Configuration: Storing shared data for all instances.
Example of a factory method:
class User {
constructor(name, role) {
this.name = name;
this.role = role;
}
static createAdmin(name) {
return new User(name, "admin");
}
}
const admin = User.createAdmin("Alice");
console.log(admin.role); // Output: admin
⭐ Key Takeaways
Static methods are defined on the class, not instances.
They're called using the class name:
ClassName.methodName()
.They can be inherited by subclasses.
They can't directly access instance properties or methods.
They're useful for utility functions, factory methods, and managing class-level data.
You can't call static methods on instances, and you can't call instance methods on the class.
▶️ Getter & Setter
Getters and setters are functions that allow you to get and set object values, respectively
//getter setter
class human {
constructor(name, age) {
this._name = name;
}
get getName() {
return this._name;
}
set setName(name) {
this._name = name;
}
}
const person = new human("", 0);
person.setName = "Raj";
person.setAge = 25;
console.log(person.getName);
console.log(person.getAge);
//Raj
//25
▶️ instanceOf Operator
Check if an object is an instance of a class, subclass, or interface
//getter setter
class human {
constructor(name, age) {
this.name = name;
this.age = age;
}
get getName() {
return this.name;
}
set setName(name) {
this.name = name;
}
get getAge() {
return this.age;
}
set setAge(age) {
this.age = age;
}
}
const person = new human("", 0);
person.setName = "Raj";
person.setAge = 25;
console.log(person.getName);
console.log(person.getAge);
const person1 = "Subham"
console.log( person instanceof human)//true
console.log( person1 instanceof human)//false
It also returns true for subclass
//getter setter
class human {
constructor(name, age) {
this.name = name;
this.age = age;
}
get getName() {
return this.name;
}
set setName(name) {
this.name = name;
}
get getAge() {
return this.age;
}
set setAge(age) {
this.age = age;
}
}
class Coder extends human {
constructor(name, age, language) {
super(name, age);
this.language = language;
}
}
const person = new human("", 0);
const subham = new Coder("subham", 22, "java");
person.setName = "Raj";
person.setAge = 25;
console.log( person instanceof human)
console.log( subham instanceof human)
I apologize for the misunderstanding. You're right, I should have been more concise. Let me provide a simpler, more focused explanation of encapsulation in JavaScript, following the pattern you've demonstrated:
▶️ Encapsulation
Encapsulation is a way to restrict the direct access to some components of an object
class BankAccount {
#balance; // Private field
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// console.log(account.#balance); // Syntax error: private field
//Encapsulation
const user = {
firstName: "John",
lastName: "Doe",
age: 25,
getAgeYear: function() {
return new Date().getFullYear() - this.age;
}
}
console.log(user.getAgeYear());
▶️ Polymorphism
Polymorphism means "many forms", and it occurs when we have many classes that are related to each other by inheritance.
// Parent class
class Animal {
makeSound() {
console.log("The animal makes a sound");
}
}
// Child classes
class Dog extends Animal {
makeSound() {
console.log("The dog barks");
}
}
class Cat extends Animal {
makeSound() {
console.log("The cat meows");
}
}
// Function to demonstrate polymorphism
function animalSound(animal) {
animal.makeSound();
}
// Usage
const animal = new Animal();
const dog = new Dog();
const cat = new Cat();
animalSound(animal); // Output: The animal makes a sound
animalSound(dog); // Output: The dog barks
animalSound(cat); // Output: The cat meows
▶️ Abstraction
Abstraction is the concept of hiding complex implementation details and showing only the necessary features of an object.
// Abstraction: Hiding complex implementation details and showing only the necessary features of an object.
// Abstract class
class Vehicle {
constructor(brand) {
this.brand = brand;
}
// Abstract method
start() {
throw new Error("Method 'start()' must be implemented.");
}
getBrand() {
return this.brand;
}
}
// Concrete class
class Car extends Vehicle {
start() {
return `${this.brand} car is starting...`;
}
}
// Usage
const myCar = new Car("Toyota");
console.log(myCar.getBrand()); // Output: Toyota
console.log(myCar.start()); // Output: Toyota car is starting...