Prototype in JavaScript
How Classes in JS/Node uses prototype
Prototype and __proto__
When working with Classes in JS, there exist a confusing concept "prototype" and sometimes in while inspecting JavaScript objects in the browser, another word can be seen __proto__. What are these? And what is their purpose in JS ecosystem. Lets explore.
What is prototype
Before answering this, lets explore what happens when a Class is created.
When you write an ES6 class in JavaScript, you’re not creating a traditional class like in Java or C#. Under the hood, JavaScript is still using constructor functions and prototypes.
When a class is created, JavaScript automatically does two important things:
-
Creates a constructor function
-
Attaches a prototype object to that constructor
This prototype object is where shared methods live.
So, what is then __proto__?
When a new instance is created using the new keyword, JavaScript sets up a hidden internal link called [[Prototype]]. This link is accessed using __proto__.
An instance’s __proto__ points to the constructor’s prototype.
Therefor based on above example:
Dog.prototype === dog1.__proto__; // trueFour Concepts You Must Seperate
When working with classes, always keep these four entities separate in your mind:
-
Class (Constructor Function)
-
Prototype (Class.prototype)
-
Instance
-
__proto__(internal prototype reference)
Understanding how these interact eliminates most confusion around JavaScript inheritance.
Lets clear the confusion arround them with an example
Case 1
class Customer {
constructor(name, email) {
this.name = name;
this.email = email;
}
}When you create an instance:
const peter = new Customer("Peter", "peter@email.com");Name and email are created on instance. Customer.prototype remains empty.
Why?
Because "this" inside the constructor refers to the instance, not the class or the prototype. Properties defined with "this" exist only on the object created by new.
The prototype is meant for shared behavior, not instance-specific data.
Lets say you want a default values for these properties, you may use static props, for example:
class Customer {
static defaultName = "default-name";
static defaultEmail = "default@email.com";
constructor(name = Customer.defaultName, email = Customer.defaultEmail) {
this.name = name;
this.email = email;
}
}const peter = new Customer("Harry", "peter@email.com");
const harry = new Customer("Harry");-
Static properties live on the constructor.
-
They do not exist on the prototype.
-
They are not inherited by instances
Customer.defaultEmail; // works
peter.defaultEmail; // undefinedClasses are blueprints: static properties describe the blueprint, not the objects created from it.
console.log(Customer);
/*
[class Customer]{
defaultName = "default-name"
defaultEmail = "default@email.com"
}
*/
console.log(Customer.prototype); // {}, Customer.prototype contains only the constructor at this point, since no methods are defined yet.
console.log(Peter); // Customer { name: "Peter", email: "peter@email.com"}
console.log(Harry); // Customer { name: "Harry", email: "default@email.com"}
console.log(Object.getPrototypeOf(peter) === Customer.prototype); // trueCase 2:
class Customer {
constructor(name, email) {
this.name = name;
this.email = email;
}
getInfo() {
return `The user named ${this.name} has email (${this.email})`;
}
}const peter = new Customer("Peter", "peter@email.com");
const harry = new Customer("Harry");Now consoling certain properties gives us:
peter.hasOwnProperty("getInfo"); // falseIt is expected as this method does not lives on instance, but lives on Class's prototype itself.
Object.getOwnPropertyNames(Customer.prototype); // ["constructor", "getInfo"]But if you console this method for instance, it gets it:
console.log(peter.getInfo()); // The user named peter has email peter@email.comBut how?
Under the hood JavaScript does the following:
-
Looks for getInfo on peter → ❌
-
Follows peter.
__proto__→ Customer.prototype -
Finds getInfo → ✅
-
Calls it with this === peter
The method is shared, but the data (this.name, this.email) is instance-specific.
As a proof, if we console log this line, it gives:
console.log(Object.getOwnPropertyNames(Peter)); // ["name", "email"]
console.log(Object.getOwnPropertyNames(Customer.prototype)); // ["constructor", "getInfo"]We can even over-ride the method for the particular instance.
peter.getInfo = function () {
return "Overridden method";
};
console.log(peter.getInfo()); // Overridden methodWhy Prototypes Matter
If methods were created inside the constructor, every instance would get its own copy — wasting memory.
By placing methods on the prototype:
-
Methods are shared
-
Memory usage is optimized
-
Behavior is consistent across instances
This is the Prototype Pattern in action in JavaScript, and it’s why JavaScript scales so well.
Key Takeaways
-
JavaScript classes are syntactic sugar over constructor functions and the prototype system.
-
Every class has a prototype object that stores shared methods.
-
Every instance has an internal [[Prototype]] link, commonly accessed as
__proto__. -
An instance’s
__proto__points to the constructor’s prototype. -
Properties defined using "this" in the constructor live only on the instance.
-
Methods defined in a class live on the prototype, not on instances.
-
JavaScript uses the prototype chain to resolve missing properties and methods.
-
Static properties live on the class itself, not on the prototype or instances.
-
Instances do not inherit static properties automatically.
-
Methods are shared across instances, improving memory efficiency.
-
Prototype methods can be overridden (shadowed) at the instance level.
-
Understanding prototype and
__proto__removes most confusion around JavaScript inheritance.