In JavaScript, the Prototype is the fundamental mechanism that enables inheritance and property sharing between objects. Unlike class-based languages (like Java or C++), where inheritance is defined through a blueprint, JavaScript uses a Prototypal Inheritance model. Every object in JavaScript has a private property that holds a link to another object called its prototype.
1. The Prototype Chain
Every JavaScript object has a private property called [[Prototype]]. This property is a link to another object, called its prototype. That prototype object has its own prototype, and so on, until an object is reached with null as its prototype. This is known as the Prototype Chain.
When you try to access a property or method on an object:
- JavaScript first looks at the object itself.
- If not found, it looks at the object’s [[Prototype]].
- It continues up the chain until it finds the property or reaches null.
2. __proto__ vs. prototype
This is the most common point of confusion for developers. They are related but serve different roles:
- __proto__ (Object Prototype): This is a property on instances. It points to the actual prototype object that the instance is currently inheriting from.
- prototype (Function Prototype): This is a property on constructor functions (or classes). It is the object that will become the __proto__ of any instance created by that function.
function Animal(name) {
this.name = name;
}
// Adding a method to the constructor's prototype
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
const dog = new Animal("Rex");
console.log(dog.__proto__ === Animal.prototype); // true
dog.speak(); // "Rex makes a noise" (found via the prototype chain)
3. Creating Objects with Prototypes
A. Constructor Functions (Legacy/Standard)
Before ES6 classes, constructor functions were the primary way to implement inheritance.
function Animal(name) {
this.name = name;
}
// Attaching a method to the prototype so all instances share it
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
const dog = new Animal("Rex");
dog.speak(); // "Rex makes a noise."
By placing speak on Animal.prototype rather than inside the Animal function itself, we ensure that every animal instance doesn’t carry a separate copy of the function in memory. They all reference the same function via the prototype.
B. ES6 Classes (Syntactic Sugar)
ES6 introduced class, which makes the prototype logic look like traditional OOP, but under the hood, it is still using the prototype chain.
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
C. Object.create()
This method allows you to create a new object and explicitly specify its prototype without using a constructor function.
const userActions = {
login() { console.log("Logged in"); }
};
const admin = Object.create(userActions);
admin.login(); // Found via prototype chain
4. Shadowing Properties
If you add a property to an object that has the same name as a property in its prototype chain, the object’s own property “shadows” (overrides) the prototype’s property.
const myDate = new Date();
console.log(myDate.getTime()); // Method from Date.prototype
// Shadowing the method
myDate.getTime = function() {
return "Time is a flat circle.";
};
console.log(myDate.getTime()); // "Time is a flat circle."
5. Modern Inheritance with Classes
ES6 introduced the class keyword, but it is important to remember that it is syntactic sugar over the prototype system. Under the hood, JavaScript is still using prototypes.
[Image showing ES6 Class syntax translated into Prototype-based code]
class Dog extends Animal {
bark() {
console.log("Woof!");
}
}
// This is exactly like:
// Dog.prototype = Object.create(Animal.prototype);
// Dog.prototype.constructor = Dog;
6. Real-World Benefit: Extending Built-ins
You can technically add your own methods to built-in prototypes like Array.prototype or String.prototype.
Warning: This is generally considered “Prototype Pollution” and is dangerous in production code because it can conflict with future JavaScript updates or other libraries.
// Adding a custom method to all arrays
Array.prototype.last = function() {
return this[this.length - 1];
};
const nums = [1, 2, 3];
console.log(nums.last()); // 3
