DEV Community

Cover image for OOPS in JS - Ultimate
Subham
Subham

Posted on • Updated on

OOPS in JS - Ultimate

🔥Connect: https://www.subham.online

🔥Repo: https://github.com/Subham-Maity/OOPS-in-JS-Ultimate

🔥GitHub: https://github.com/Subham-Maity

🔥Twitter: https://twitter.com/TheSubhamMaity

🔥LinkedIn: https://www.linkedin.com/in/subham-xam

🔥Insta: https://www.instagram.com/subham_xam


✅ Looking for the best notes on OOP concepts in JAVA? Here’s one for you:

> "OOPS in JAVA - Ultimate" - Check This

▶️ Intro

⭐ What is OOPs ?

Object-oriented programming is an approach to solving problems by creating objects .

⭐ 4 Pillars of OOP

Terminologies in OOP

  1. Abstraction - Hiding internal details (show only essential info!)
  2. Encapsulation - The act of putting various components together (in a capsule)
  3. Inheritance - The act of deriving new things from existing things
  4. Polymorphism - One entity, many forms

js

▶️ 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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

▶️ 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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

⭐ Types of Constructors

  1. Non-Parameterized Constructor: A constructor that has no arguments.
    class Example {
        constructor() {
            this.property = "default value";
        }
    }
Enter fullscreen mode Exit fullscreen mode
  1. Parameterized Constructor: A constructor that takes parameters.
    class Example {
        constructor(value) {
            this.property = value;
        }
    }
Enter fullscreen mode Exit fullscreen mode
  1. 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();
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

⭐ Types of Inheritance

  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. 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
Enter fullscreen mode Exit fullscreen mode

▶️ 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
Enter fullscreen mode Exit fullscreen mode

⭐ 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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

⭐ Key Points of Method Overriding

  1. Same Method Name: The method in the child class must have the same name as in the parent class.

  2. Same Parameters: The method in the child class must have the same parameter list as the parent class method.

  3. IS-A Relationship: Method overriding only occurs in two classes that have an IS-A relationship (inheritance).

  4. Access Modifiers: The overriding method can have a less restrictive access modifier, but not a more restrictive one.

  5. 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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

▶️ 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

▶️ Access Modifiers

Access modifier is a keyword that is used to set the accessibility of a class member

⭐ Types of Access Modifiers

  1. Public: Members declared as public are accessible from any other class.
  2. Protected: Members declared as protected are accessible within the same class and by derived class instances.
  3. 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
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

In this scenario:

  • _protectedProperty and _protectedMethod are accessible within Parent
  • They'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();
Enter fullscreen mode Exit fullscreen mode

In this case:

  • #privateProperty and #privateMethod are only accessible within Parent
  • They're not accessible in Child, even though it extends Parent
  • They're not accessible outside the class at all

Key Takeaways

  1. Public members (default) are accessible everywhere.
  2. Protected members (convention with _) are accessible within the class and subclasses, but shouldn't be accessed outside (though technically they can be).
  3. Private members (with #) are only accessible within the defining class, not in subclasses or outside.
  4. When using protected members, they behave like public members in terms of accessibility, but developers agree to treat them as if they were protected.
  5. 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
Enter fullscreen mode Exit fullscreen mode

Key points:

  1. The capitalize method is declared as static using the static keyword.
  2. It's called on the class (Animal.capitalize) not on instances.
  3. 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!
Enter fullscreen mode Exit fullscreen mode

Note:

  1. The Human class inherits the static capitalize method from Animal.
  2. Human can also define its own static methods, like greet.

⭐ 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
Enter fullscreen mode Exit fullscreen mode

⭐ 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());
Enter fullscreen mode Exit fullscreen mode

⭐ Use Cases for Static Methods

  1. Utility Functions: Methods that don't require object state.
  2. Factory Methods: Creating instances with special properties.
  3. 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
Enter fullscreen mode Exit fullscreen mode

⭐ Key Takeaways

  1. Static methods are defined on the class, not instances.
  2. They're called using the class name: ClassName.methodName().
  3. They can be inherited by subclasses.
  4. They can't directly access instance properties or methods.
  5. They're useful for utility functions, factory methods, and managing class-level data.
  6. 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
Enter fullscreen mode Exit fullscreen mode

▶️ 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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

▶️ 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
Enter fullscreen mode Exit fullscreen mode
//Encapsulation
const user = {
    firstName: "John",
    lastName: "Doe",
    age: 25,
    getAgeYear: function() {
        return new Date().getFullYear() - this.age;
    }
}
console.log(user.getAgeYear());
Enter fullscreen mode Exit fullscreen mode

▶️ 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
Enter fullscreen mode Exit fullscreen mode

▶️ 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...
Enter fullscreen mode Exit fullscreen mode

Top comments (21)

Collapse
 
lionelrowe profile image
lionel-rowe

JS is actually a POOPS language

Prototypical
Object
Oriented
Programming
Style

Collapse
 
brense profile image
Rense Bakker

Poopies! 💩

Sorry 😜

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

4 Pillars of OOP

This is definitely the prevalent school of thought in the field of Object Oriented Programming, and it probably used to be even more so 20 or 30 years ago.

Fundamentally, OOP only has one pillar: You bundle data and related subroutines into a single logical entity: the Object.

From there, classes emerge from the observation that we often need many objects with the same behaviours but different data, and inheritance is only one way of keeping code DRY by allowing us to write generic code once in generic classes, then having other classes build on that.

These concepts have quickly become "the way we do things" because they all make sense, but that doesn't mean they are the one and only way of doing OOP, and JavaScript shows that quite well.

Javascript is, at its core, a prototype-based OO language, not a class-based one. In recent years it has just started to offer lots of syntax sugar to implement class-based OO on top of prototype-based OO.

The class keyword is really just a convenient way to switch around the prototype and constructor: In JavaScript, the constructor has an attached prototype that it sets on the objects it creates. But using class allows us to write the prototype and define the constructor in a nested block. But all that really does is make the code easier to think about in terms of classes and instances. Under the hood, classes are still just functions.

Nice article though 👍

Collapse
 
tomasdevs profile image
Tomas Stveracek

Good read! I have a question: Is it still important to learn OOP in JavaScript, when functional programming is becoming more popular? Many new frameworks and libraries use functional concepts. Should we focus on OOP or move more towards functional programming? What do you think?

Collapse
 
codexam profile image
Subham

That’s a really good question! I get why functional programming is getting popular, especially with things like React hooks, but I think OOP still has its place, especially in bigger projects. Like with NestJS, which is super OOPs focused — it’s great for building scalable, maintainable backend systems. It uses stuff like classes and dependency injection, which can really help manage complex code. Honestly, it’s not like you have to choose one over the other. Most modern codebases kind of blend both, depending on what makes the most sense for the problem you’re solving.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

JavaScript is currently seeing a repeat of the trend where everything had to be shoved into the OOP paradigm whether if git or not, now with FP concepts.

The truth is, both have their moments and dogmatically trying to only use one over the other is an easy way to waste time, specially in a context where you're getting paid to build a product.

A good developer should be familiar with both and judge based on the situation which tool is the right one for the job. Sometimes that'll be FP, sometimes it'll be OOP, and more often than either of those, it's gonna be a bit of a mix.

It's also not really true that actual FP is really becoming all that popular; people usually throw that word around when they just mean higher order functions, but they often ignore other aspects of it like immutability, pure functions, etc. because writing code that is actually functional and performant in JavaScript is surprisingly difficult.

Collapse
 
efpage profile image
Eckehard

How do you get this in Javascript?

Key Points of Method Overriding:

  1. Same Parameters: The method in the child class must have the same parameter list as the parent class method.

Even declaring public methods "by convention" seems to be a strange concept. Some of this concepts in OO-languages are used to enable better checks by the compiler. But in JS nobody will protect you from doing thing wrong...

Collapse
 
codexam profile image
Subham

JavaScript does indeed handle things differently from stricter OOPs languages. Method overriding in JavaScript works through the prototype chain/inheritance.

Note: When you create a class (or constructor function) in JavaScript, it sets up a prototype chain. When a child class extends a parent class, it links their prototypes. When you call a method on an object, JavaScript first looks for that method on the object itself. If it doesn't find it, it looks up the prototype chain.

While it doesn't enforce the same rigid rules, we can still apply similar principles. In JS, we override methods simply by defining them in the child class with the same name. The beauty (and potential danger) is that JS doesn't fuss about matching parameters or access levels, it's all on us to keep things consistent.

Here's what we can do (but probably shouldn't):

class Parent {
  greet(name) {
    console.log(`Hello, ${name}!`);
  }
}

class Child extends Parent {
  greet(name, age) {  // Different parameters, JS doesn't complain
    console.log(`Hi ${name}, you're ${age} years old!`);
  }
}
Enter fullscreen mode Exit fullscreen mode

And here's a better approach we should follow:

class Parent {
  greet(name) {
    console.log(`Hello, ${name}!`);
  }
}

class Child extends Parent {
  greet(name) {  // Same parameters as parent
    super.greet(name);  // Call parent method
    console.log("How are you doing?");
  }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
efpage profile image
Eckehard

In my experience, OOP rules serve mainly to protect a programmer's against his worst enemy: Himselve! Regardless how good your style is, if you revisit a code month later - or are lucky to even reuse a larger portion of another project that was battle tested - you are happy if your code contains some barriers against unwanted mutation.

The worst things are often done with a good intention!

Maybe I´m wrong, but even the prototype chain is pretty careless with respect to types. I´m not aware that even the number of parameters matters. As long as a childs method was found in the parent, it is overwritten, regardless how it was defined.

Collapse
 
abdulmuminyqn profile image
Abdulmumin yaqeen

Well written!

I use OOP in most of my python projects, but with JS I haven't found a use for it in my websites 😅. It not for the average Web developer.

Collapse
 
komsenapati profile image
K Om Senapati

You can use it in the backend and in React.

Collapse
 
rahul_mishra_a61b74c90dab profile image
rahul mishra

great article, covers almost everything.

Collapse
 
prashant-ardeshana profile image
Prashant Ardeshana

Nice 👍

Collapse
 
chnjames profile image
James Scott

subham.online
Your website looks great, I want to know how you achieved it? Can you share it?

Collapse
 
ashnunez profile image
AshNunez

I like the article and especially that Ben10 was use as an example. 😆

Collapse
 
mannuelf profile image
Mannuel

Great overview🤘