Prototype and Inheritance in JS


In programming, inheritance refers to passing down characteristics from a parent to a child so that a new piece of code can reuse and build upon the features of an existing one. JavaScript implements inheritance by using objects. Each object has an internal link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null
as its prototype. By definition, null
has no prototype and acts as the final link in this prototype chain.
Prototype Inheritance β The Family Genetics Analogy π¨βπ©βπ§βπ¦π§¬
Imagine a family where physical traits and skills pass down from one generation to the next. This is just like how JavaScript objects inherit properties and methods from their prototype.
Meet Grandpa Tom β The Original Prototype π΄π§¬
Grandpa Tom is the origin of the family traits. He has some special characteristics:
β
Blue eyes
β
Curly hair
β
Good at woodworking
Since Grandpa Tom is the first in the lineage, these traits become the prototype traits of the family.
Dad Mike Inherits from Grandpa π§
Dad Mike is Grandpa Tomβs son. He inherits most of his fatherβs traits automatically:
β Blue eyes (Inherited from Grandpa)
β Curly hair (Inherited from Grandpa)
β Good at woodworking (Inherited from Grandpa)
β Great at painting (New skill, not from Grandpa)
Even though he didnβt explicitly define "blue eyes" or "curly hair," they are inherited from Grandpa.
Little Timmy β The Next Generation π¦
Now, Dad Mike has a son, Little Timmy. He automatically inherits traits, but he can also have his own modifications.
β Blue eyes (Inherited from Grandpa Tom)
β Curly hair (Inherited from Grandpa Tom)
β Good at woodworking (Inherited from Grandpa Tom)
β Great at painting (Inherited from Dad Mike)
β Loves coding (New unique trait of Timmy)
Timmy doesn't redefine his eye color or hair type because he inherits them from his ancestors. However, he adds his own unique skill (coding).
How This Relates to JavaScript Prototypes
Grandpa Tom = Base prototype (e.g.,
Object.prototype
)Dad Mike = Intermediate prototype (e.g., Parent Object like
Animal.prototype
)Little Timmy = Child object (Specific instance, like
Dog
)Adding a new trait (Timmy loves coding) = Overriding or adding new properties in an object
If a trait isnβt found in Timmy, JavaScript looks at Dad, then Grandpa (Prototype Chain)
Code Example Based on This Story
javascriptCopyEdit// Grandpa Tom - Base Prototype
function Grandpa() {
this.eyeColor = "Blue";
this.hairType = "Curly";
}
Grandpa.prototype.woodworkingSkill = function() {
console.log("I am great at woodworking!");
};
// Dad Mike - Inheriting from Grandpa
function Dad() {
Grandpa.call(this); // Inherit properties
this.extraSkill = "Painting"; // Unique to Dad
}
// Inherit prototype methods
Dad.prototype = Object.create(Grandpa.prototype);
Dad.prototype.constructor = Dad;
// Timmy - Inheriting from Dad
function Timmy() {
Dad.call(this); // Inherit properties
this.uniqueSkill = "Coding"; // Unique to Timmy
}
// Inherit prototype methods
Timmy.prototype = Object.create(Dad.prototype);
Timmy.prototype.constructor = Timmy;
// Creating instances
const timmy = new Timmy();
console.log(timmy.eyeColor); // "Blue" (Inherited from Grandpa)
console.log(timmy.hairType); // "Curly" (Inherited from Grandpa)
console.log(timmy.extraSkill); // "Painting" (Inherited from Dad)
console.log(timmy.uniqueSkill); // "Coding" (Timmy's own skill)
timmy.woodworkingSkill(); // "I am great at woodworking!" (Inherited from Grandpa)
Prototype Chain in Action ποΈ
When JavaScript looks for
timmy.eyeColor
, it doesnβt find it in Timmy.It looks up in Dad's prototype β still not found.
It looks up in Grandpa's prototype and finds it there β "Blue".
prototype
vs __proto__
in JavaScript
In JavaScript, both prototype
and __proto__
are related to prototype-based inheritance, but they serve different purposes. Letβs break it down clearly.
1οΈβ£ prototype
(Function Prototype)
πΉ Exists on functions (only on constructor functions, NOT regular objects).
πΉ Used to define shared properties/methods for all objects created from a constructor.
πΉ Allows objects created from a function to inherit methods and properties.
Example of prototype
javascriptCopyEdit// Constructor function
function Person(name) {
this.name = name;
}
// Adding a method using prototype
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
// Creating objects
const john = new Person("John");
const alice = new Person("Alice");
// Both objects inherit greet from Person.prototype
john.greet(); // Output: Hello, my name is John
alice.greet(); // Output: Hello, my name is Alice
πΉ Here, Person.prototype
is used to store the greet
method, and all instances of Person
inherit it.
πΉ This avoids copying the method for every object, making memory usage efficient.
2οΈβ£ __proto__
(Dunder Proto)
πΉ Exists on every object and points to its prototype (inherited object).
πΉ Used for prototype chain lookups when accessing properties or methods.
πΉ Itβs an accessor to the internal prototype, but modifying it directly is discouraged.
Example of __proto__
javascriptCopyEditconst obj = { key: "value" };
console.log(obj.__proto__); // Points to Object.prototype
console.log(obj.__proto__ === Object.prototype); // true
You can also manually set __proto__
, though itβs not recommended:
javascriptCopyEditconst parent = { greet: function() { console.log("Hello!"); } };
const child = {};
child.__proto__ = parent; // Manually setting prototype
child.greet(); // Output: Hello! (Inherited from parent)
πΉ Better alternative to setting __proto__
directly:
javascriptCopyEditconst child = Object.create(parent);
child.greet(); // Output: Hello!
This properly sets up inheritance.
Key Differences Between prototype
and __proto__
Feature | prototype | __proto__ |
Type | Property of functions (only function objects have it) | Property of all objects |
Purpose | Used to define methods and properties for instances | Used to access the prototype (inherited object) |
Usage | Defines shared methods for objects | Helps in prototype chaining (property lookup) |
Modifiable? | Yes, you can add methods to it | Can be modified, but not recommended |
Example | Person.prototype.greet = function() {...} | child.__proto__ = parent (not recommended) |
Final Analogy π
prototype
is like a recipe book for a restaurant chain:Every restaurant (object) follows the same recipe (method stored in
prototype
).If you change the recipe (
prototype
), it updates for all future restaurants.
__proto__
is like asking your parents about a tradition:If you (object) donβt have a specific skill (property), you ask your parents (
__proto__
) if they do.If they donβt have it, they ask their parents (prototype chain continues).
When to Use What?
β
Use prototype
when defining shared methods in constructor functions.
β
Use Object.create(prototypeObject)
instead of modifying __proto__
.
β Avoid modifying __proto__
directly because itβs slower and considered bad practice.
Achieving Inheritance in JavaScript Without Using extends
Even without the extends
keyword (which is used in ES6 classes), JavaScript still allows inheritance through prototypes. Below are different ways to achieve inheritance without using extends
.
1οΈβ£ Using Constructor Functions and Object.create()
This is the classic way to achieve inheritance before ES6 classes.
Example:
javascriptCopyEdit// Parent Constructor Function
function Animal(name) {
this.name = name;
}
// Adding method to Animal prototype
Animal.prototype.makeSound = function() {
console.log("Some generic animal sound");
};
// Child Constructor Function
function Dog(name, breed) {
Animal.call(this, name); // Inherit properties from Animal
this.breed = breed;
}
// Inheriting methods from Animal prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// Adding a new method specific to Dog
Dog.prototype.bark = function() {
console.log("Woof! Woof!");
};
// Creating an instance
const myDog = new Dog("Buddy", "Golden Retriever");
console.log(myDog.name); // Buddy (Inherited property)
myDog.makeSound(); // Some generic animal sound (Inherited method)
myDog.bark(); // Woof! Woof! (Dog's own method)
How It Works?
Animal.call
(this, name)
copies the properties fromAnimal
toDog
.Dog.prototype = Object.create(Animal.prototype)
linksDog
toAnimal
for prototype-based inheritance.Now,
myDog
inheritsmakeSound()
fromAnimal
but can also have its ownbark()
method.
2οΈβ£ Using Object.create()
Directly (Prototypal Inheritance)
This approach creates objects without using constructor functions.
Example:
javascriptCopyEdit// Parent object
const Animal = {
makeSound: function() {
console.log("Some generic animal sound");
}
};
// Creating a new object and inheriting from Animal
const Dog = Object.create(Animal);
Dog.bark = function() {
console.log("Woof! Woof!");
};
// Creating an instance
const myDog = Object.create(Dog);
myDog.makeSound(); // Inherited from Animal: "Some generic animal sound"
myDog.bark(); // Woof! Woof!
How It Works?
Object.create(Animal)
setsAnimal
as the prototype ofDog
.Object.create(Dog)
setsDog
as the prototype ofmyDog
.This creates a prototype chain where
myDog
first looks forbark()
, and if not found, it looks inAnimal
.
3οΈβ£ Using Class-Like Syntax Without extends
Even though we're not using extends
, we can still create a class-like structure using constructor functions and prototypes.
Example:
javascriptCopyEdit// Parent class (function-based)
function Animal(name) {
this.name = name;
}
// Adding method to prototype
Animal.prototype.makeSound = function() {
console.log(`${this.name} makes a sound`);
};
// Child class without using extends
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
// Manually set up prototype inheritance
Object.setPrototypeOf(Dog.prototype, Animal.prototype);
// Adding new method to Dog
Dog.prototype.bark = function() {
console.log(`${this.name} barks: Woof! Woof!`);
};
// Creating an instance
const myDog = new Dog("Buddy", "Golden Retriever");
myDog.makeSound(); // Buddy makes a sound
myDog.bark(); // Buddy barks: Woof! Woof!
How It Works?
Animal.call
(this, name)
calls the parent constructor.Object.setPrototypeOf(Dog.prototype, Animal.prototype)
manually sets up the inheritance.myDog
gets access to bothmakeSound()
fromAnimal
andbark()
fromDog
.
β
Object.create()
β Best for pure prototype-based inheritance.
β
Constructor Functions with Object.create(Parent.prototype)
β Closest to ES6 extends
.
β
Object.setPrototypeOf()
β Explicitly sets up inheritance but is less common.
JavaScript Events Explained with a Real-World Analogy π¦π
Think of JavaScript events like real-world events happening around you. Just like how we listen and respond to events in daily life, JavaScript can detect and react to user interactions like clicks, keypresses, or mouse movements.
π Real-World Analogy: A Birthday Party π
Imagine you are at a birthday party π. Different events happen at the party, and depending on what happens, you take action.
1οΈβ£ Event: Someone Rings the Doorbell πͺπ
Real-world: When a guest arrives, they ring the doorbell.
JavaScript: This is like a "click" event on a button.
Response: You hear the bell and open the door (event handler runs).
javascriptCopyEditdocument.getElementById("doorbell").addEventListener("click", function() {
console.log("Someone rang the doorbell! Open the door.");
});
2οΈβ£ Event: Someone Blows Out the Birthday Candles ππ¨
Real-world: When the birthday person blows out the candles, people clap.
JavaScript: This is like a "keypress" event (e.g., pressing Enter to submit a form).
Response: People clap and cheer (event handler executes).
javascriptCopyEditdocument.addEventListener("keypress", function(event) {
if (event.key === "Enter") {
console.log("π The candles are blown out! Everyone claps!");
}
});
3οΈβ£ Event: The Music Starts Playing π΅π§
Real-world: When the DJ starts playing music, people start dancing.
JavaScript: This is like a "change" event (e.g., selecting a different song from a dropdown).
Response: People react to the music change.
javascriptCopyEditdocument.getElementById("musicSelect").addEventListener("change", function() {
console.log("πΆ New song selected! People start dancing!");
});
4οΈβ£ Event: Someone Spills a Drink πΉπ¦
Real-world: When someone spills a drink, a waiter quickly cleans it up.
JavaScript: This is like an "error" event (e.g., an image fails to load).
Response: A waiter (event handler) jumps in to fix it.
javascriptCopyEditdocument.getElementById("partyImage").addEventListener("error", function() {
console.log("Oops! The image didn't load. Show a backup image.");
});
5οΈβ£ Event: A Guest Leaves the Party πΆββοΈπ
Real-world: When someone says goodbye, the host thanks them for coming.
JavaScript: This is like a "mouseleave" event (e.g., moving the mouse away from an element).
Response: Display a "Thanks for visiting!" message.
javascriptCopyEditdocument.getElementById("guestArea").addEventListener("mouseleave", function() {
console.log("Thanks for coming! See you next time!");
});
Summary Table π
Real-World Event | JavaScript Event | Response (Event Listener) |
Ringing the doorbell πͺπ | click | Open the door |
Blowing out candles ππ¨ | keypress (Enter) | People clap |
Changing music π΅π§ | change | People dance |
Spilling a drink πΉπ¦ | error | Waiter cleans up |
Guest leaving πΆββοΈ | mouseleave | Host says goodbye |
Events are things that happen in the browser, just like real-life events.
JavaScript lets us listen for these events and respond accordingly.
We use event listeners (
addEventListener
) to trigger actions when events occur.
Subscribe to my newsletter
Read articles from Kartikey Katyal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
