Prototype & Inheritance in JavaScript
From Blueprints to Code: Grasping JavaScript Prototypes
Hey JavaScript friends! 👋 Still scratching your head about prototypes and inheritance? Don't worry, you are SO not alone! It can feel like some secret code language at first.
But guess what? It’s actually pretty straightforward. We just need the right way to think about it. And today, we're gonna use cities! Yup, cities! Let's build our understanding city by city.
Imagine This: City Blueprints and Standard Services 🏘️ (Prototype Analogy)
Think about building a city. You wouldn't just randomly throw buildings together, right? You'd start with a blueprint, a plan.
And what about basic things every city needs? Like… roads! Most cities are gonna need roads. So, it makes sense to have "roads" as part of a standard city services package, something that comes with almost every city blueprint.
Think of a constructor function in JavaScript like a city blueprint. It's the plan for creating city objects. And that standard city services package, which includes "roads"? That’s the prototype! It’s where all the shared, basic stuff for cities lives.
// Our City Blueprint (Constructor Function)
function City(name) {
this.name = name;
}
// Let's add a "checkCityServices" ability to the Standard City Services Package (prototype)!
City.prototype.checkCityServices = function() {
return `${this.name} should have basic city services... like roads.`; // Roads are standard!
};
// Creating cities using the blueprint (using 'new')
const mumbai = new City("Mumbai");
const tokyo = new City("Tokyo");
console.log(mumbai.checkCityServices()); // Output: Mumbai should have basic city services... like roads.
console.log(tokyo.checkCityServices()); // Output: Tokkyo should have basic city services... like roads.
In this example:
City
is our city blueprint (constructor function).City.prototype
is like the Standard City Services Package. It holds shared features and abilities for allCity
objects, like the idea that cities have roads.checkCityServices
is a method in the Standard City Services Package (City.prototype
). So, every city made withnew City()
automatically knows how tocheckCityServices
and will find "roads" as a standard service.
prototype
vs. __proto__
🤺
Imagine you have a "Standard City Services Package". This package lists all the basic services that most cities should have, like "roads," maybe "water access," etc.
Now, think of City.prototype
as the actual box where you keep this "Standard City Services Package". It's like the storage container for all those shared services. It's on the City Blueprint (City
constructor).
And then, think of __proto__
(that sneaky double underscore thing) as a secret service pipeline connected to each city you build. Every time you build a new city using new City()
, it automatically gets one of these pipelines installed. This pipeline doesn't contain the services itself. Instead, it's like a direct line that connects the city back to that "Standard City Services Package" box (City.prototype
).
So, in simple terms:
City.prototype
is the box of services. It contains the "Standard City Services Package." It's where those services are defined and stored. It's attached to the blueprint (City
).__proto__
is the service pipeline for each city. It's a connection from each city object back to that "box of services" (City.prototype
). It's how each city gets access to the services in the package. It's attached to the city object itself (like mumbai, tokyo, etc.).
Think of it like this simple table:
Feature | prototype (the "box") | __proto__ (the "pipeline") |
What is it like? | A box, a container, a storage unit. | A pipeline, a connection, a link. |
What does it hold? | The "Standard City Services Package" (like methods and properties - in our case, checkCityServices ). | Nothing directly. It just points to the "box" (City.prototype ). |
Where is it found? | On the City Blueprint (City constructor). | On every city object you create (like mumbai, tokyo). |
What's its job? | To store and define the shared services. | To connect each city to the shared services so they can use them. |
Analogy Recap:
City.prototype
: The "Standard City Services Package" box.__proto__
: The service pipeline from each city back to the "services box."
prototype
vs. __proto__
- Tracing the Services Back to the Package 📦 (Let's think "box" and "pipeline" instead of "tracing back")
Let's get clear on prototype
and __proto__
. They're related, but they're not the same thing. Think of them like:
Feature | prototype | __proto__ (aka [[Prototype]] ) |
Where to find it | On the City Blueprint (constructor function, class). | On actual cities (objects). |
What it IS | The Standard City Services Package (the BOX). Contains shared features for all cities built from this blueprint. | A secret services connection (the PIPELINE) from each city backto the Standard City Services Package (the blueprint's prototype!). |
Why it's important | To define the basic, shared stuff that most cities will have (like roads). | To let cities inherit those standard services. If a city needs to know about "roads," it can check back through its service connection (pipeline!) to the Standard City Services Package! (Prototype chaining!) |
How to access it | City.prototype | cityObject.__proto__ (but Object.getPrototypeOf(cityObject) is cleaner now) |
City Services Analogy | The Standard City Services Package (the BOX). | Imagine each city having a tiny, invisible services pipeline (the PIPELINE) leading back to the Standard City Services Package! (That pipeline is __proto__ ) |
Key takeaway: prototype
is the storage box of shared services. __proto__
is the pipeline that lets each city access that box."New" in the Picture - Building the Service Pipeline! 🛠️
new
:
What does the new
keyword actually do when we make cities? It’s more than just saying "build a city!" One key thing new
does is set up that __proto__
service pipeline.
When you write const myCity = new City("MyCity")
, here’s kinda what happens behind the scenes:
A brand new, empty city object is created.
A services pipeline (
__proto__
) is built into this new city, and it points straight toCity.prototype
(the Standard City Services Package). This pipeline is how inheritance works!The
City
blueprint (constructor) is used to set up basic city info (like thename
).Voila! You've got a city with a service pipeline connected to the Standard Services Package.
Let's see that service pipeline in code with our cities:
function City(name) {
this.name = name;
}
City.prototype.checkCityServices = function() {
return `${this.name} should have basic services, like roads.`;
};
const london = new City("London");
console.log(london.__proto__ === City.prototype); // Output: true! Service pipeline confirmed!
console.log(Object.getPrototypeOf(london) === City.prototype); // Output: true! (Cleaner way to see the pipeline!)
Yep, when we ran new City("London")
, london.__proto__
got linked up to City.prototype
. That’s how london
can use checkCityServices
, even though that function isn't directly inside the london
object itself. It inherited it through the prototype chain (the service pipeline!).
Inheritance Without extends
– Adding Specialized City Services 🌇
JavaScript inheritance is all about prototypes. Forget about classes for a minute (ES6 classes are just built on top of prototypes to make them look class-like!). Think about cities becoming more specialized.
Instead of extends
(which is just a shortcut), we can make inheritance happen directly with prototypes. Let’s say we want to create a PortCity
that’s a type of City
, but it has special services related to being a port. It'll still start with the basic City
blueprint but add its own port-specific services.
function City(name) {
this.name = name;
}
City.prototype.checkCityServices = function() {
return `${this.name} should have basic services, like roads.`;
};
function PortCity(name, portType) {
City.call(this, name); // Use the basic "city setup" from the City blueprint
this.portType = portType; // Add the specific "portType" feature for PortCity
}
// Set up the "services pipeline": PortCity gets basic services from City
PortCity.prototype = Object.create(City.prototype);
PortCity.prototype.constructor = PortCity; // Important: Make sure the blueprint points back to PortCity!
PortCity.prototype.checkCityServices = function() { // Let's make checkCityServices more specific for port cities!
return `${City.prototype.checkCityServices.call(this)} As a port city, it also specializes in ${this.portType} port services.`;
};
const shanghai = new PortCity("Shanghai", "container");
console.log(shanghai.checkCityServices()); // Output: Shanghai should have basic services, like roads. As a port city, it also specializes in container port services.
console.log(shanghai instanceof City); // Output: true! Shanghai IS a type of City, right?
What's going on in this code?
PortCity
Blueprint: We made a blueprint forPortCity
. We useCity.call
(this, name)
to borrow the basicCity
setup (name, etc.).Object.create(City.prototype)
: This is the inheritance magic!Object.create()
creates a new, empty thing whose__proto__
(service pipeline!) points toCity.prototype
. Then, we setPortCity.prototype
to be this new thing. NowPortCity
inherits fromCity
!PortCity.prototype.constructor = PortCity;
: Super important! We fix theconstructor
property ofPortCity.prototype
to point back toPortCity
. Otherwise, things get confused.Making
checkCityServices
Specialized: We changed thecheckCityServices
method inPortCity.prototype
to talk about port services. We even usedCity.prototype.checkCityServices.call
(this)
to get the originalcity services message and then add to it.
JavaScript Events: Like City-Wide Announcements! 📢
Last part – JavaScript Events! Let's think about city-wide announcements. These are events that happen in a city, and people react.
City Announcement Happens (the Event): The city makes an announcement - maybe about a road closure, a festival, etc. This is like a user clicking a button or typing something on a webpage.
City Announcement Boards (Event Targets): Think of announcement boards all over the city, or the city website. These are like HTML elements (buttons, text boxes) that can "listen" for events.
Citizens (Event Listeners): People living in the city are looking at those announcement boards or checking the website, listening for announcements. This is like attaching an event listener to a button:
button.addEventListener('click', ...)
.Changing Plans (Event Handler Functions): When a city announcement goes out (like a road closure), citizens might change their travel plans or attend the festival. This is your event handler function – what happens when the event occurs.
<button id="announceButton">Check City Announcements</button>
<script>
const announceButton = document.getElementById('announceButton');
announceButton.addEventListener('click', function() { // Listening for a 'click' (like checking for a city announcement)
alert("City Announcement: JavaScript Prototypes are Awesome!"); // Event handler - what happens when clicked (announcement is shown)
});
</script>
Just like city announcements, JavaScript events are all about:
Something happening (an event)
Someone paying attention (event listener)
Someone doing something in response (event handler)
Prototypes, Inheritance, Events: City-Building Mode! 🏙️
Hopefully, the city blueprint and services analogy made things clearer this time around! Maybe it feels less like a puzzle and more like… building with LEGOs?
Prototypes are like Standard City Services Packages (shared features).
__proto__
is the service pipeline linking cities to those packages (inheritance).JavaScript events are like city announcements that trigger actions.
Thank you for reading! Until next time! 🙌