JavaScript’s object-oriented programming is built on a unique and powerful concept called prototypes. Unlike many classical languages that use classes, JavaScript uses prototypes to enable objects to inherit properties and methods from other objects. This system allows objects to share behavior efficiently without duplicating code for each instance. Understanding prototypes is key to mastering how JavaScript organizes and reuses functionality in object-oriented designs.
At its core, every JavaScript object has a prototype — a hidden link to another object from which it can inherit properties and methods. When you try to access a property on an object, JavaScript first looks at that object’s own properties. If it doesn’t find it, it follows the prototype link to check the prototype object, continuing up the chain until it either finds the property or reaches the end. This chain of prototypes is known as the prototype chain, and it forms the foundation of JavaScript’s inheritance model.
Accessing and Setting an Object’s Prototype
To start working with prototypes, it helps to know how to access and change an object’s prototype. JavaScript provides Object.getPrototypeOf()
to check an object’s prototype, and Object.setPrototypeOf()
to change it. There is also a less formal way, the __proto__
property, which behaves similarly.
Consider this simple example where we create a plain object and inspect its prototype:
const animal = { eats: true };
console.log(Object.getPrototypeOf(animal)); // Output: {}
const objProto = Object.getPrototypeOf(animal);
console.log(objProto === Object.prototype); // Output: true
Here, animal
is a simple object with one property, eats
. When we get its prototype, we find it points to Object.prototype
, the root prototype that most objects inherit from. This confirms that animal
inherits properties and methods from the base object prototype.
Adding Methods to an Object’s Prototype
When using constructor functions, prototypes become very useful. Each constructor has a .prototype
property. By adding methods to this property, all instances created from that constructor share those methods. This avoids repeating the same function for every object.
Let’s define a constructor for Cat
objects and add a method meow
to its prototype:
function Cat(name) {
this.name = name;
}
Cat.prototype.meow = function() {
console.log(this.name + " says Meow!");
};
const felix = new Cat("Felix");
felix.meow(); // Output: Felix says Meow!
const garfield = new Cat("Garfield");
garfield.meow(); // Output: Garfield says Meow!
The method meow
is only defined once on Cat.prototype
. Both felix
and garfield
can call it, and this
inside the method refers to the calling object, allowing each cat to say its own name.
Prototype Chain in Action
JavaScript uses the prototype chain to look up properties not found directly on the object. When you try to access a property, the engine searches the object itself, then its prototype, then that prototype’s prototype, and so forth until it finds the property or reaches null
.
Let’s see how this works with an example:
const parent = {
walk: function () {
console.log("Walking...");
},
};
const child = Object.create(parent);
child.walk(); // Output: Walking...
console.log(child.hasOwnProperty("walk")); // Output: false
console.log(parent.isPrototypeOf(child)); // Output: true
Here, child
has no direct walk
method, but its prototype, parent
, does. Calling child.walk()
works because the prototype chain leads to parent
. The hasOwnProperty
check confirms that walk
is not directly on child
, and isPrototypeOf
verifies the prototype link. The Object.create
method lets you create a new object with the specified object as its prototype, may be null. If the prototype is null
, it means the new object does not inherit from any other object.
Overriding Prototype Properties and Methods
An important feature of prototypes is that objects can override inherited properties or methods by defining their own versions. When this happens, JavaScript uses the object’s own property instead of searching the prototype chain.
Let’s override the meow
method for one specific Cat
instance:
function Cat(name) {
this.name = name;
}
Cat.prototype.meow = function() {
console.log(this.name + " says Meow!");
};
const felix = new Cat("Felix");
const tom = new Cat("Tom");
tom.meow = function() {
console.log(this.name + " growls instead of meowing!");
};
tom.meow(); // Output: Tom growls instead of meowing!
felix.meow(); // Output: Felix says Meow!
In this example, tom
replaces the shared meow
method with its own version. Calling tom.meow()
triggers the overridden function, while felix
still uses the prototype’s original method. This shows how instances can have unique behavior while sharing a common prototype.
Using Object.create() to Set Prototypes
Another way to set an object’s prototype is by using Object.create()
. This method creates a new object with a specified prototype without needing a constructor function. It’s a straightforward way to build prototype chains manually.
Let’s create a base prototype with shared behavior and create objects inheriting from it:
const animalProto = {
speak: function() {
console.log(this.name + " makes a sound.");
}
};
const dog = Object.create(animalProto);
dog.name = "Rex";
dog.speak(); // Output: Rex makes a sound.
Here, dog
is created with animalProto
as its prototype. It can call speak
even though it does not define it directly. This method offers a clean way to build prototypes without constructors.
Prototypes in Built-in JavaScript Objects
JavaScript’s built-in objects such as Array
, String
, and Number
also use prototypes to share methods. You can even add your own methods to these prototypes.
For example, let’s add a custom method to Array.prototype
:
Array.prototype.first = function() {
return this[0];
};
const fruits = ["Mango", "Banana", "Papaya"];
console.log(fruits.first()); // Output: Mango
All arrays now have access to the first
method, which returns the first element. This example highlights how prototype chains allow powerful extensions of built-in objects.
Fun Example: A Family of Animals with Shared Behaviors
To wrap up, let’s build a lively example that uses prototypes to model a family of animals. We’ll create a shared Animal
prototype and then create Dog
and Cat
objects inheriting from it with some overrides.
const Animal = {
speak: function() {
console.log(this.name + " makes a noise.");
},
eat: function() {
console.log(this.name + " is eating.");
}
};
const dog = Object.create(Animal);
dog.name = "Buddy";
dog.speak = function() {
console.log(this.name + " barks loudly!");
};
const cat = Object.create(Animal);
cat.name = "Whiskers";
dog.speak(); // Output: Buddy barks loudly!
dog.eat(); // Output: Buddy is eating.
cat.speak(); // Output: Whiskers makes a noise.
cat.eat(); // Output: Whiskers is eating.
The dog
overrides speak
while cat
uses the inherited version. Both share the eat
method from Animal
. This simple family shows prototypes at work creating shared and unique behavior in objects.
Conclusion
Prototypes are the backbone of JavaScript’s object system. They allow objects to inherit properties and methods, enabling efficient code reuse. We explored how to access and set prototypes, add shared methods, and how property lookup happens along the prototype chain. We also saw how to override prototype properties and create objects with Object.create()
. Finally, our animal family example illustrated prototypes in a fun, practical way.
Mastering prototypes unlocks deeper understanding of JavaScript’s inheritance and is essential for working effectively with objects.
References
If you want to learn more about JavaScript prototypes and inheritance, these resources are excellent starting points:
- MDN Web Docs: Inheritance and the Prototype Chain
The official documentation explaining prototype chains and inheritance. - MDN Web Docs: Object.getPrototypeOf()
Learn how to retrieve the prototype of an object. - JavaScript Info: Prototypes
A detailed tutorial on how prototype inheritance works in JavaScript. - MDN Web Docs: Object.create()
Understand how to create objects with specified prototypes.