Prototypes & Object-Oriented JavaScript: A Complete Beginner's Guide
JavaScript is one of the most popular programming languages for web development. One of its most powerful features is its support for Object-Oriented Programming (OOP). Unlike languages such as Java or C++, JavaScript uses a prototype-based inheritance model. In ES6, JavaScript introduced classes, making OOP easier to understand while still using prototypes internally.
In this blog, we'll explore Prototypes, Prototype Chain, Object.create(), Classes, Inheritance, Static Members, Getters & Setters, and Encapsulation with simple examples.
What is Object-Oriented Programming (OOP)?
Object-Oriented Programming is a programming style where we organize code using objects.
An object contains:
- Properties (Data)
- Methods (Functions)
Example
const student = {
name: "Sai",
age: 21,
study() {
console.log("Studying JavaScript...");
}
};
Here,
-
nameandageare properties -
study()is a method
Memory representation:
student
│
├── name → "Sai"
├── age → 21
└── study() → Function
Objects help us group related data and behavior together.
The Problem Without Prototypes
Imagine creating multiple student objects.
const student1 = {
name: "Sai",
study() {
console.log("Studying...");
}
};
const student2 = {
name: "Rahul",
study() {
console.log("Studying...");
}
};
Both objects contain their own copy of the study() function.
If you create 1000 students, JavaScript creates 1000 identical functions.
student1
│
└── study()
student2
│
└── study()
student3
│
└── study()
This wastes memory.
JavaScript solves this using Prototypes.
What is a Prototype?
A prototype is an object that allows other objects to inherit its properties and methods.
Instead of copying methods into every object, JavaScript stores them once in the prototype.
Example
const person = {
greet() {
console.log("Hello!");
}
};
const student = Object.create(person);
student.name = "Sai";
student.greet();
Output
Hello!
Memory
student
│
├── name → Sai
│
▼
Prototype
person
│
└── greet()
Notice that greet() exists only once.
What is the Prototype Chain?
Whenever JavaScript cannot find a property inside an object, it searches the object's prototype.
This search continues until the property is found or until JavaScript reaches null.
Example
const person = {
greet() {
console.log("Hello");
}
};
const student = Object.create(person);
student.greet();
Search process:
student
↓
person
↓
Object.prototype
↓
null
This searching mechanism is called the Prototype Chain.
Understanding Object.create()
Object.create() creates a new object using another object as its prototype.
Syntax
Object.create(prototypeObject)
Example
const animal = {
eat() {
console.log("Eating...");
}
};
const dog = Object.create(animal);
dog.eat();
Output
Eating...
Memory
dog
↓
animal
↓
eat()
This promotes code reuse.
Constructor Functions
Before ES6 classes, JavaScript developers used constructor functions.
Example
function Student(name, age) {
this.name = name;
this.age = age;
}
const s1 = new Student("Sai", 21);
const s2 = new Student("Rahul", 20);
Methods were attached using the prototype.
Student.prototype.study = function () {
console.log("Studying...");
};
Both objects now share one copy of study().
Classes in JavaScript
ES6 introduced the class keyword.
A class is simply syntactic sugar over JavaScript's prototype system.
Example
class Student {
constructor(name, age) {
this.name = name;
this.age = age;
}
study() {
console.log("Studying...");
}
}
Creating an object
const s1 = new Student("Sai", 21);
Internally JavaScript still creates prototype-based objects.
Understanding the Constructor
The constructor initializes object properties.
Example
class Student {
constructor(name) {
this.name = name;
}
}
const s = new Student("Sai");
Whenever new Student() is executed, the constructor runs automatically.
Inheritance
Inheritance allows one class to acquire properties and methods from another class.
JavaScript uses the extends keyword.
Example
class Animal {
eat() {
console.log("Eating...");
}
}
class Dog extends Animal {
}
const d = new Dog();
d.eat();
Output
Eating...
Memory
Dog
↓
Animal
↓
eat()
Dog inherits the eat() method from Animal.
The super Keyword
When a child class defines its own constructor, it must call the parent constructor using super().
Example
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
}
Without super(), JavaScript throws an error because the parent object has not been initialized.
Static Members
Static methods belong to the class itself rather than its objects.
Example
class Calculator {
static add(a, b) {
return a + b;
}
}
console.log(Calculator.add(10, 20));
Output
30
Notice we call the method using the class name.
Calculator.add()
✔ Correct
new Calculator().add()
✖ Wrong
Static methods are useful for utility functions.
Getters and Setters
Getters allow us to read data like a property while executing a function.
Example
class Student {
constructor(name) {
this._name = name;
}
get name() {
return this._name;
}
}
const s = new Student("Sai");
console.log(s.name);
Output
Sai
Setter example
class Student {
constructor(name) {
this._name = name;
}
set name(value) {
this._name = value;
}
}
const s = new Student("Sai");
s.name = "Rahul";
console.log(s.name);
Output
Rahul
Setters are commonly used for validation.
Encapsulation
Encapsulation means hiding internal data and exposing only controlled methods to access or modify it.
A real-life example is an ATM.
You cannot directly change your account balance.
Instead, you use operations such as:
- Deposit
- Withdraw
- Check Balance
Encapsulation Using Closures
Before private fields existed, JavaScript used closures.
function Bank() {
let balance = 1000;
return {
deposit(amount) {
balance += amount;
},
getBalance() {
return balance;
}
};
}
const account = Bank();
account.deposit(500);
console.log(account.getBalance());
Output
1500
Trying to access
console.log(account.balance);
Output
undefined
The variable balance remains private.
Private Fields (#)
Modern JavaScript introduced private class fields using #.
Example
class Bank {
#balance = 1000;
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const account = new Bank();
console.log(account.getBalance());
Output
1000
Trying to access
console.log(account.#balance);
produces a syntax error because private fields are accessible only inside the class.
Prototype vs Class
| Prototype | Class |
|---|---|
| Core inheritance mechanism | Cleaner ES6 syntax |
| Uses prototype objects | Uses class keyword |
| More verbose | Easier to read |
| Supports inheritance | Internally uses prototypes |
Closures vs Private Fields
| Closures | Private Fields |
|---|---|
| Function-based | Class-based |
| Uses lexical scope | Uses # syntax |
| Older approach | Modern approach |
| Good for factory functions | Best for classes |
Key Takeaways
- JavaScript objects store data and behavior together.
- Prototypes allow multiple objects to share methods efficiently.
- The Prototype Chain is how JavaScript searches for missing properties.
-
Object.create()creates objects that inherit from other objects. - Classes provide a cleaner syntax while still using prototypes internally.
- Inheritance allows child classes to reuse parent functionality.
-
super()calls the parent constructor. - Static methods belong to the class, not its instances.
- Getters and setters provide controlled access to object properties.
- Encapsulation protects internal data using closures or private fields (
#).
Conclusion
Understanding prototypes is the foundation of mastering JavaScript's object-oriented features. While ES6 classes make the language easier to read and write, it's important to remember that classes are simply a cleaner syntax built on top of JavaScript's prototype system. Once you understand prototypes, concepts like inheritance, encapsulation, static methods, getters, and setters become much easier to grasp.
Top comments (0)