JavaScript is different from many other programming languages when it comes to Object-Oriented Programming (OOP). Instead of using classes as its foundation, JavaScript uses prototypes. ES6 introduced classes, but they are just a cleaner way of working with prototypes.

In this blog, I'll explain the topics I learned today.

1. What is a Prototype?

A prototype is a shared object that stores common methods and properties.

Instead of creating a separate copy of the same method for every object, JavaScript stores it once in the prototype, and all objects created from the same constructor or class share it.

Example

function Person(name) {
this.name = name;
}

Person.prototype.greet = function () {
console.log(Hello, I'm ${this.name});
};

const p1 = new Person("Harshith");
const p2 = new Person("Rahul");

p1.greet();
p2.greet();

Here, both p1 and p2 use the same greet() method from Person.prototype.

This saves memory because JavaScript doesn't create multiple copies of the same function.

2. Prototype vs __proto__

This topic confused me at first, but here's the difference.

prototype

  • Exists on functions.
  • Used when creating new objects with the new keyword.
  • Stores shared methods.

Example:

function Person(name) {
this.name = name;
}

Person.prototype.greet = function () {};
const p1 = new Person("Harshith");
p1 will have a property called "proto" there it stores the prototype of Person
p1.proto=Person.prototype;

__proto__

  • Exists on objects.
  • Points to the object's prototype.
  • JavaScript follows this link while searching for properties.

Example:

const person = new Person("Harshith");

console.log(person.proto === Person.prototype);

A simple way to remember:

  • prototype belongs to functions.
  • __proto__ belongs to objects.

3. Prototype Chain

When JavaScript cannot find a property inside an object, it searches its prototype.

If it is still not found, it continues searching through the next prototype until it reaches null.

Example:

const animal = {
eat() {
console.log("Eating");
}
};

const dog = Object.create(animal);

dog.eat();

JavaScript searches like this:

dog

animal.prototype

null

This searching process is called the Prototype Chain.

4. Object.create()

Object.create() creates a new object whose prototype is another object.

Example:

const animal = {
eat() {
console.log("Eating");
}
};

const dog = Object.create(animal);

dog.eat();

The dog object doesn't have its own eat() method.

It inherits it from animal.


5. Classes are Syntactic Sugar

Before ES6, constructor functions were used to create objects.

function Person(name) {
this.name = name;
}
Person.prototype.greet=function (){
console.log(this.name);
}

Now we can write:

class Person {
constructor(name) {
this.name = name;
}

greet() {
    console.log(this.name);
}

Enter fullscreen mode Exit fullscreen mode

}

Both work almost the same internally.

A class is simply a cleaner and easier syntax built on top of JavaScript's prototype system.


6. Inheritance

Inheritance allows one class to reuse the properties and methods of another class.

Example:

class Animal {
eat() {
console.log("Eating");
}
}

class Dog extends Animal {}

const dog = new Dog();

dog.eat();

Since Dog extends Animal, it inherits the eat() method.

This helps us avoid writing the same code again.


7. Polymorphism

Polymorphism means one method can behave differently depending on the object that calls it.

In JavaScript, this is usually achieved by overriding a parent class method in a child class.

Example

class Animal {
speak() {
console.log("Animal makes a sound");
}
}

class Dog extends Animal {
speak() {
console.log("Dog barks");
}
}

class Cat extends Animal {
speak() {
console.log("Cat meows");
}
}

const dog = new Dog();
const cat = new Cat();

dog.speak();
cat.speak();

Output:

Dog barks
Cat meows

Here, all classes have the same speak() method, but each class provides its own implementation. This is called polymorphism.


8. Abstraction

Abstraction means showing only the necessary functionality and hiding the internal implementation.

For example, when we start a car, we only press the start button. We don't need to know how the engine, fuel system, and battery work internally.

JavaScript allows us to achieve abstraction using classes and private methods or private fields.

Example

class Car {
start() {
this.#checkFuel();
console.log("Car Started");
}

#checkFuel() {
    console.log("Checking Fuel...");
}

Enter fullscreen mode Exit fullscreen mode

}

const car = new Car();

car.start();

Output:

Checking Fuel...
Car Started

Enter fullscreen mode Exit fullscreen mode

The user only calls start(). The internal method #checkFuel() remains hidden from outside the class. This makes the code easier to use and protects the internal implementation.

9. Static Members

Normally, we create an object before calling a method.

const obj = new Person();

But some methods belong to the class itself, not to its objects.

These are called static methods.

Example:

class MathUtil {
static add(a, b) {
return a + b;
}
}

console.log(MathUtil.add(5, 3));

Notice that we didn't create an object.


10. Getters and Setters

Getters and setters allow us to control how properties are read and updated.

Example:

class Person {
constructor(name) {
this._name = name;
}
get name() {
return this._name;
}

set name(value) {
    if (value.length >= 3) {
        this._name = value;
    }
}

Enter fullscreen mode Exit fullscreen mode

}

This helps us validate data before updating it.


11. Encapsulation Using Closures

Encapsulation means hiding data so it cannot be changed directly.

Example:

function BankAccount() {
let balance = 1000;
return {
deposit(amount) {
balance += amount;
},

    getBalance() {
        return balance;
    }
};

Enter fullscreen mode Exit fullscreen mode

}
const account = BankAccount();
account.deposit(500);
console.log(account.getBalance());

The variable balance cannot be accessed directly from outside the function.


12. Private Fields (#field)

Modern JavaScript provides another way to hide data using private fields.

Example:

class BankAccount {
#balance = 1000;
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount();
account.deposit(500);
console.log(account.getBalance());
Trying to access:
account.#balance;

will cause an error because private fields can only be accessed inside the class.

Conclusion

At first, JavaScript prototypes can feel confusing because there are terms like prototype, __proto__, constructor functions, and classes. But once I understood that objects inherit methods through prototypes, everything started making sense.

Now I know that JavaScript's OOP system is built on prototypes, and classes simply make the syntax cleaner and easier to read. Understanding these concepts gives a strong foundation for writing better JavaScript code.