Prototypes In JavaScript
Every JavaScript Object Has a Story. It Starts with a Prototype.
Introduction
In JavaScript, everything is an object, and every object has a prototype. This prototype serves as a hidden link, allowing objects to inherit properties and methods from one another. Whether it's an array, a function, or even a primitive wrapped as an object, each entity in JavaScript is connected through this prototype chain.
JavaScript prototype is an object that contains properties and methods that are shared among all instances of a particular object. It was designed to implement inheritance in JavaScript, which follows a prototype-based rather than a class-based approach like other languages.
Every JavaScript Object has a prototype property, which contains the prototype of that object.
The Prototype is a reference to another object and is used when a property or method is called on an object.
In this article— we explain what a prototype is, how prototype chains work, and how a prototype for an object can be set.
Prototype Inheritance
Create an object literal; which looks like this
const myObject = {
city: "Mawsynram",
greet() {
console.log(`Namaste from ${this.city} !`);
},
};
myObject.greet(); // Namaste from Mawsynram !
This is an object with one property city
and one method greet()
. If you call object’s name followed by a period like myObject.
, you will get various properties available to this myObject
.
There you will see city
and greet()
as well as a lot of other properties!.
If you try to access one of them.
For example:
myObject.toString(); // "[object Object]"
What are these extra properties, and where do they come from?
Well, as i said earlier every object has a built-in property which is called it's prototype.
The prototype is itself an object, so the prototype will have its own prototype, making what's called a prototype chain.
The chain breaks when a certain prototype has it’s prototype value to null
.
Think of it like in this way: a blueprint of a car, if you can build multiple cars from the same blueprint they will have all the same features, like wheels and an engine. You can customize each car (object) without changing the original blueprint (prototype).
When you try to access a property in any object (in case of this myObject
) JS engine first finds in object, if the property can't be found in the object itself, the prototype is searched for the property.
If the property still can’t be found the prototype’s prototype is searched and so on until either the property is found or the end of the chain is reached, in which case undefined
is returned.
Quick recap: when we call for myObject.toString()
the browser (if in console) or JS engine:
looks for object own properties
can’t find there, so looks in prototype of the object for toString
finds it there, and calls it.
Analyzing Prototype’s of Prototype
If you want to check prototype of any object or anything here your getPrototypeOf()
is your best friend.
For example:
Object.getPrototypeOf(myObject); // Object { }
This is an object called Object.prototype
, and it is the most basic prototype, that all objects have by default. The prototype of Object.prototype
is null
, so it's at the end of the prototype chain:
[[Prototype]] vs .prototype vs __proto__ (Dunder Proto)
Understanding prototyping
Consider this code below:
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
getName() {
return `${this.make} ${this.model}`;
}
}
const fordInstance = new Car('Ford', 'Mustang');
console.log(fordInstance.getName()); // Ford Mustang
Now, the visual easy-to-understand presentation:
The algorithm is next: when we define a class or function in our code the JS engine creates a new object (prototype object, empty be default) simultaneously and allocates memory for it.
You can check this by just typing this line in the console:
console.dir(function func() {})
When the JS interpreter reaches the moment when we call our function through the keyword ‘new’ (the same for regular funciton with ‘new’ as well). The JS engine manages this behaviour not as a usual function.
Classes in JS are a syntactic sugar for Functions, the logic is pretty much the same. So, after the class Car was invoked, it creates a new object with certain fields in it. We place that object in the fordInstance
variable. Also, the engine sets a connection between instance and Car.prototype
via the internal [[Prototype]] mechanism. The developer doesn’t have any access to it at all. But, we are able to manipulate prototypes with __proto__
.
__proto__
is a getter/setter for [[Prototype]]. And where can we get it?
Simply: using the same prototype chain! Going through it, we can get a Object.prototype at the end, which has getter/setter __proto__
.
We are able to call it straight from fordInstance
and get our Car.prototype
. Check the code below.
console.dir(fordInstance.__proto__) // Car.prototype
console.dir(Object.getPrototypeOf(fordInstance)) // The same, just modern way
Since this is not only getter but setter, we can change our prototype. And I recommend to do it rarely and wisely (You may brake a prototype chain!) [read this more about in chapter Modifying Prototypes].
And guess the most important thing is that each object in JS (everything in JS is an object) has __proto__
! And always refers to the prototype of the class with which it was created. Remember this rule in ages. I will describe it in details on the next paragraph.
Okay, but what about .prototype?
The main thing about .prototype, is that it might be only in either functions or class. Try to remember this as well.
As i said earlier, its another object, which was attached to the fordInstance
after it was created.
That object contains all methods of our car, and it’s a really convenient way to organize them, otherwise we would have all of these methods inside each instance, and this is the worst memory usage.
Quick recap: [[Prototype]] is an internal mechanism of prototype inheritance. You may treat it as a regular reference, but without access to it. On the other hand, browsers give us the ability to adjust prototype chain via __proto__
. Almost in all cases, you may think that [[Prototype]] and __proto__
are equal.
These are only links to the prototype! And prototype is an object referenced by __proto__
. If there is no method within instance, engine will check it in prototypes till the end using prototype inheritance.
Each object has __proto__
.
Each function contains .prototype and __proto__
as well.
Going Through From Some Examples
console.log({}.__proto__ === {}.prototype);
It’s false. When we use an object literal its the same as new Object(). Do you remember what’s going on when we call a function with new keyword. Right, it attaches instance ({} in our case) to the prototype (which is Object.prototype)! And as I mentioned, you can call .prototype only in functions, so the right part of the assignment is undefined
. Hence, answer is falsy.
Next condition returns true:
console.log({}.__proto__ === Object.prototype); // true
What about this example:
console.log([].__proto__ === Object.prototype); ?
As you might guess, its false. Because []. __proto__
is equal to new Array(). __proto__
. And new Array().__proto__ === Array.prototype
because [] is instance of Array. On other hand, Array.prototype.__proto__ === Object.prototype
, because Array.prototype is a regular object, and as a usual object it has a reference to the Object.prototype.
These conditions are both true:
console.log([].__proto__ === Array.prototype);
console.log([].__proto__.__proto__ === Object.prototype);
The same behaviour here:
console.log((10).__proto__ === Number.prototype); // its true
We created 10 in a straightforward way, but under the hood JS will use new Number(10). number is a primitive value, but when we call it through the “dot”, JS converts it in object ( it’s called autoboxing). That’s why a prototype chain works with primitive types as well.
Like I provided some example below:
console.log((20).toString()) // 20 as a string type
console.log('string'.repeat(2)) // 'stringstring'
Classical Inheritance vs Prototype-Based Inheritance
Feature | Classical Inheritance | Prototype-Based Inheritance |
Definition | Inheritance is based on classes, where objects are instantiated from a class blueprint. | Inheritance is based on prototypes, where objects inherit directly from other objects. |
Structure | Uses class hierarchies (e.g., Java, C++, Python). | Uses prototype chains (e.g., JavaScript). |
Object Creation | Objects are created from classes using constructors. | Objects are created from other objects using Object.create() or constructor functions. |
Method Sharing | Methods are defined in a class and shared across instances. | Methods are stored in the prototype and shared among objects. |
Inheritance Model | Uses a strict, hierarchical inheritance (parent-child relationship). | Uses flexible, dynamic delegation (objects link to prototypes). |
Modification | Changing a class affects all instances, but modifying an instance does not affect the class. | Modifying the prototype affects all linked objects. |
Performance | Can be optimized for performance in languages like Java and C++. | Prototype lookup can be slightly slower due to delegation. |
Example (JS - Classical) | class Animal {} → class Dog extends Animal {} | const dog = Object.create(animalPrototype); |
Shadowing Properties
What happens if you define a property in an object, when a property with the same name is defined in the object's prototype?
Let’s see:
const myDate = new Date(1995, 11, 17);
console.log(myDate.getTime()); // 819129600000
myDate.getTime = function () {
console.log("something else!");
};
myDate.getTime(); // 'something else!
This should be predictable, given the description of the prototype chain. When we call getTime()
the browser first looks in myDate
for a property with that name, and only checks the prototype if myDate
does not define it. So when we add getTime()
to myDate
, then the version in myDate
is called.
Think of it like in this way: Imagine in a school where teacher does not know your real name so, she calls you out with your nick name instead of your first name. Here your nick name is local property and then she wants to know your real name so, she checks your first name in official record which is prototype.
This is called "shadowing" the property.
It occurs when a property or a method in an object is overrides an inherited property or a method from it’s own prototype.
This means the object's own property takes precedence over the one in the prototype chain.
For example:
let parent = {
name : "Momo",
}
let child = Object.create(parent)
child.name = "Okarun, got balls"; // Shadows the 'name' property from the prototype
console.log(child.name); // "Okarun, got balls" (not "Momo")
console.log(parent.name); // "Momo" (remains unchanged)
Here, child.name
shadows parent.name
, meaning the child object’s property is used instead of the inherited one.
Modifying Prototypes
In JavaScript, prototypes define an object’s behavior, but what if you want to extend or modify that behavior?
You can modify, change and even overrides a prototype.
For example: Consider this code below:
const Member1 = {
codeName: "Twilight",
identification: "Loid Forger, A Psychiatrist",
work: function () {
return this.codeName + " A Spy"
},
};
const Member2 = {
codeName: "FullMetal Lady",
identification: "WISE Commanding Officer",
work: function () {
return this.codeName + " Work: Handler"
},
};
Member2.__proto__ = Member1;
It’s an intriguing example, if you run this code
console.log(Member2.work()); // FullMetal Lady Work: Handler
In this Member2.__proto__ = Member1
Member2 is an object which prototype’s is referring to Member1 prototype by using __proto__
.
Since Member2
has its own work()
function, it shadows Member1
’s method.
So, When Member2.work
is deleted, JavaScript looks up the prototype chain and finds work()
in Member1
.
// FullMetal Lady A Spy
The problem with this approach is it is breaking DRY (Don't Repeat Yourself) principle.
Likewise, the work
method is duplicated both in Member1
and Member2
. Instead of redefining work()
in every object, you should define it once and let both objects inherit it.
Adding Method to A Prototype
One of the biggest advantages of prototypes is that they allow multiple instances to share the same functionality without allocating extra memory. Instead of attaching methods directly to each object, we attach them to the prototype, ensuring all instances inherit the behavior.
function Person(name) {
this.name = name;
}
// Adding a method to Person’s prototype
Person.prototype.greet = function () {
console.log(`Hello, my name is ${this.name}.`);
};
const Okarun= new Person("Okarun");
Okarun.greet(); // "Hello, my name is Okarun."
Here greet()
is once defined in Person’s prototype, so every instance of person can use it without creating a new whole object or a separate copy.
Reshaping Built-in Prototypes
It’s a double edges sword; a superpower but also a potential nightmare if used incorrectly.
For example: We can change Arrays and Objects prototypes. Likewise, consider this code below which is adding reverse method to Strings.
String.prototype.reverse = function () {
return this.split("").reverse().join("");
};
console.log("hello".reverse()); // "olleh"
It is similar to a polyfill in this manner.(but they don’t override methods)
I recommend to do this merely. (As may it leads Security Vulnerabilities!). Also it affects every instance of that type.
Functions and Classes
Functions in JS are tricky. They may have __proto__
and prototype at the same time. If you call a function without ‘new’ keyword, you DON’T need to think about prototype at all. Simply because you don’t create a new instance, you treat that function as a usual instruction to do something. And all functions, except arrow functions, have its own prototype.
Lets talk about __proto__
in functions. Functions in JS are objects too, therefore they can get a __proto__
. Here’s some code and 2 images for better understanding:
function usualFunction() {}
console.log(usualFunction.__proto__ === Function.prototype); // true
console.log(usualFunction.call === Function.call); // true
console.log(usualFunction.valueOf === Object.prototype.valueOf); // true
Let’s spend a few minutes and discuss about __proto__ in built-in classes.
There are a lot of built-in classes in JS, such as String, Number, Promise, Function, Object, Array, Date etc. All of these classes are functions. And they may have a __proto__
and a .prototype simultaneously. If we call them with new, we stick their prototypes to the new instance. But what about __proto__
?
[[Prototype]] or just __proto__
(or dunder proto) in functions is completely another thing than .prototype. You should remember, that __proto__
always refers to the prototype of the class with which it was created. But how are other classes/functions created? Of course, with the new() Function. In JS only functions can create other functions. All of these built-in classes were created via function. That’s why each __proto__
of these classes refers to the Function.prototype.
You may ensure:
console.log(String.__proto__ === Function.prototype); // true
console.log(Number.__proto__ === Function.prototype); // true
console.log(Promise.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Array.__proto__ === Function.prototype); // true
console.log(Date.__proto__ === Function.prototype); // true
Lets also describe this part of code:
class Animal {
walk() {}
}
class Cat extends Animal {
meow() {};
}
const catInstance = new Cat();
Here we have class Animal. Since this is a class it has its own prototype and a proto (Animal.__proto__ === Function.prototype)
, because class Animal itself was created with new Function() under the hood. The more interesting thing is with class Cat. It has prototype too, which is connected to the parent prototype (Animal.prototype). But what about proto? We need to check, how class Cat was created. And in this case it was born from class Animal (extends keyword). Hence, Cat.__proto__ === Animal
.
Let me show you the scheme:
It looks complex at first glance. Don’t be afraid. Red lines are the main prototype chain. Green lines — the reference from class to its own prototype. Yellow — the reference to the constructor, which created this instance. Eventually you can check it your browser and play with it.
Like I mentioned before, the end of the prototype chain is always Object.prototype. Parent of each class/function is Function.prototype.
Weird behaviour with prototype
I’m gonna show you the really weird behaviour with prototypes. Try to put this code in your console:
console.log(Function.prototype, typeof Function.prototype === 'function'); // true
console.log(Array.prototype, Array.isArray(Array.prototype)); // true
You may ask: Hey, you said that prototype is always an object, why do we see a function and an array respectively? I know it looks quite strange. But what if I say that you should treat them as regular objects! It was done by ECMA due to backward compatibility. You may read about it here and here.
Conclusion
proto is a getter/setter for [[Prototype]]
prototype is always object
The object's own property takes precedence over the one in the prototype chain is known as shadowing property.
All objects have
__proto__
Only functions have prototype, except array functions, async functions and some others
When you call function without new keyword you don’t need its prototype
.prototype of function doesn’t affect on function itself, its only a prototype object for new instances
Try to avoid manipulations with prototypes manually
End of each prototype chain is Object.prototype
Despite prototypes and ‘this’ mostly be next to each other, its completely different independent parts.
And the most important: try to play with above examples in your browser. Use console.dir for convenient revealing each prototype.
Want more?
Read my more blogs here !
Connect with me at X(twitter),
Github