Skip to content

Prototypes and Object-Oriented Programming

Object-oriented programming (OOP) organizes code using objects that contain both data and behavior.

In JavaScript, OOP is prototype-based, not class-based.

Even when using class, JavaScript internally relies on prototype chains.


Reuse

Write logic once, reuse everywhere

Structure

Organize code cleanly

Scalability

Easier to extend large systems

Performance

Shared prototype methods save memory

graph TD A[Instance] --> B[Prototype] B --> C[Prototype] C --> D[null]

  1. Check object itself
  2. If not found → check prototype
  3. Continue upward
  4. Stop at null

const animal = {
speak() {
return "Some sound";
},
};
const dog = Object.create(animal);
dog.name = "Bruno";
console.log(dog.speak());

graph LR A[dog] -->|no speak| B[animal] B -->|found| C[execute]

const baseUser = { role: "user" };
const admin = Object.create(baseUser);
admin.name = "Riya";
console.log(admin.name); // own
console.log(admin.role); // prototype

admin.role = "admin";

Now:

console.log(admin.role); // admin

__proto__

Actual link to parent (runtime)

prototype

Property of constructor

[[Prototype]]

Internal engine slot


function Person() {}
const p = new Person();
p.__proto__ === Person.prototype; // true

function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
return `Hi ${this.name}`;
};

graph TD A[p1] --> B[Person.prototype] C[p2] --> B

function User(name) {
this.name = name;
}
User.prototype.role = "user";
const u1 = new User("A");
const u2 = new User("B");
u1.role = "admin";
console.log(u1.role); // admin
console.log(u2.role); // user

  • u1.role → own property created
  • u2.role → still prototype

const user = {
first: "Sahil",
last: "Kumar",
get fullName() {
return `${this.first} ${this.last}`;
},
set fullName(value) {
[this.first, this.last] = value.split(" ");
},
};

console.log(user.fullName);
user.fullName = "John Doe";

function Person(name) {
this.name = name;
}
Person.prototype = {
get display() {
return `Name: ${this.name}`;
},
};

const obj = { a: 1 };
obj.hasOwnProperty("a"); // true
"a" in obj; // true

  • hasOwnProperty → own only
  • in → includes prototype

class Vehicle {
constructor(brand) {
this.brand = brand;
}
start() {
return `${this.brand} started`;
}
}

Vehicle.prototype.start = function () {};

class Animal {
speak() {
return "sound";
}
}
class Dog extends Animal {
bark() {
return "woof";
}
}

graph TD A[Dog] --> B[Animal] B --> C[Object]

  1. Encapsulation → bundle data + methods
  2. Inheritance → reuse logic
  3. Polymorphism → same method, different behavior
  4. Abstraction → hide complexity

class BankAccount {
#balance = 0;
deposit(a) {
this.#balance += a;
}
getBalance() {
return this.#balance;
}
}

class Shape {
area() {
return 0;
}
}

User.prototype.say = function () {
return this.name;
};

const obj1 = Object.create(proto);
const obj2 = new Constructor();

Object.setPrototypeOf(obj, proto);

function Test() {}
Test.prototype.arr = [];
const a = new Test();
const b = new Test();
a.arr.push(1);
console.log(b.arr); // [1]

class ApiClient {
async get() {
return fetch("/api").then((r) => r.json());
}
}