JavaScript’s object system is unique compared to many classical languages because it is based on prototypical inheritance. Unlike classical inheritance where classes inherit from other classes, JavaScript objects inherit directly from other objects through a prototype chain. This model allows objects to share properties and methods by linking to a prototype object, which can then link to another prototype, and so forth. Understanding prototypical inheritance is essential to mastering JavaScript’s flexible and powerful object system.
In this article, we will explore how to use prototypical inheritance in JavaScript by focusing on practical “how-to” examples. You will learn to create objects that inherit properties and methods from other objects, how to override inherited behavior, and how prototype chains work behind the scenes. The examples are designed to be fun and engaging, helping you see inheritance in action with relatable and creative code.
Creating an Object with a Prototype Using Object.create()
One of the simplest ways to set up prototypical inheritance in JavaScript is by using the built-in Object.create()
method. This method creates a new object, setting the prototype of that object to another object you specify.
Let’s start by creating a basic object named Car
. This Car
object will have some properties such as make
and year
. Then, we will create a new object named Tesla
that inherits from Car
and adds its own unique property.
const Car = {
make: "Generic",
year: 2000,
startEngine() {
return `The ${this.make} engine has started.`;
}
};
const Tesla = Object.create(Car);
Tesla.make = "Tesla";
Tesla.year = 2025;
Tesla.batteryLife = "500 miles";
console.log(Tesla.startEngine()); // Inherited method
console.log(`Battery life: ${Tesla.batteryLife}`); // Own property
Here, Tesla
is created with Car
as its prototype. This means Tesla
inherits the startEngine
method from Car
. We override the make
and year
properties to reflect Tesla’s identity and add a new property, batteryLife
. When we call Tesla.startEngine()
, JavaScript looks up startEngine
on Tesla
. Since it’s not directly on Tesla
, it checks the prototype (Car
) and finds it there, allowing Tesla to use the method without redefining it.
Adding Properties and Methods to Prototypes
When working with objects created via constructor functions or Object.create()
, you can add shared methods and properties to their prototype so all derived objects share the same behavior without duplicating code.
Suppose we want to create a constructor function CarConstructor
and add a shared method to its prototype.
function CarConstructor(make, year) {
this.make = make;
this.year = year;
}
CarConstructor.prototype.startEngine = function () {
return `The ${this.make} engine roars to life!`;
};
const ford = new CarConstructor("Ford", 2018);
console.log(ford.startEngine());
In this example, the startEngine
method is added to CarConstructor.prototype
. Any object created with new CarConstructor()
automatically inherits this method. Calling ford.startEngine()
works because JavaScript follows the prototype chain to find it.
Overriding Prototype Methods in Child Objects
Sometimes you want to customize or replace a method inherited from a prototype. JavaScript allows you to override prototype methods by simply defining a method with the same name directly on the child object.
Using the previous Car
example, let’s override startEngine
in a new object Tesla
.
const Car = {
make: "Generic",
year: 2000,
startEngine() {
return `The ${this.make} engine has started.`;
}
};
const Tesla2 = Object.create(Car);
Tesla2.make = "Tesla";
Tesla2.startEngine = function () {
return `The ${this.make}'s silent electric engine hums softly.`;
};
console.log(Tesla2.startEngine()); // Overridden method
Here, although Tesla2
inherits from Car
, it replaces the inherited startEngine
method with its own version. Now, calling Tesla2.startEngine()
executes this new method, demonstrating how overrides work.
Using Constructor Functions and Prototypes for Inheritance
Before ES6 classes, constructor functions paired with prototypes were the main way to implement inheritance in JavaScript. Constructor functions create new objects, and shared methods are stored on their prototype.
Let’s create an Animal
constructor with a shared method.
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
return `${this.name} makes a sound.`;
};
const dog = new Animal("Kol");
console.log(dog.speak());
This code creates an Animal
object named dog
. The speak
method is shared among all Animal
instances, kept on the prototype to avoid duplication.
Setting Up Inheritance Between Constructor Functions
Inheritance between constructor functions requires setting the prototype of the child constructor to an instance of the parent’s prototype. This creates the prototype chain.
Let’s create a Dog
constructor that inherits from Animal
.
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () {
return `${this.name} barks loudly!`;
};
const bulldog = new Dog("Bruno", "Bulldog");
console.log(bulldog.speak()); // Inherited method
console.log(bulldog.bark()); // Child method
Here, Dog
calls Animal
’s constructor to set name
. Then we set Dog.prototype
to inherit from Animal.prototype
. After that, we fix the constructor
property to point back to Dog
. Now, bulldog
can access both speak
from Animal
and bark
defined on Dog
.
Calling Parent Prototype Methods in Child Objects
Sometimes a child method needs to reuse the parent’s method. This can be done by explicitly calling the parent prototype’s method with the child’s context.
In the Dog
example, let’s override speak
but call the parent method inside it.
Dog.prototype.speak = function () {
const parentSpeak = Animal.prototype.speak.call(this);
return `${parentSpeak} Also, ${this.name} wags its tail.`;
};
console.log(bulldog.speak());
This code overrides speak
in Dog
. It first calls the parent’s speak
using call(this)
to keep the right context, then adds additional behavior. The output combines both messages, showing method reuse.
Prototype Chain Exploration and Property Lookup
JavaScript looks for properties and methods starting from the object itself, then up the prototype chain until it finds the property or reaches the end (null
).
Let’s create a deeper chain: Animal
→ Dog
→ Bulldog
.
function Bulldog(name) {
Dog.call(this, name, "Bulldog");
}
Bulldog.prototype = Object.create(Dog.prototype);
Bulldog.prototype.constructor = Bulldog;
Bulldog.prototype.snore = function () {
return `${this.name} snores loudly. Zzz!`;
};
const max = new Bulldog("Max");
console.log(max.bark()); // From Dog
console.log(max.speak()); // Overridden in Dog
console.log(max.snore()); // From Bulldog
max
has access to methods from all levels. When max.bark()
is called, JavaScript looks on max
, then Bulldog.prototype
, then Dog.prototype
and finds bark
. This demonstrates how the prototype chain directs property lookup.
Fun Example: Building a Magical Creature Prototype Chain
To illustrate prototypical inheritance in a playful way, let’s create a chain of magical creatures.
const Creature = {
type: "Creature",
describe() {
return `A mysterious ${this.type} wanders the forest.`;
}
};
const Elf = Object.create(Creature);
Elf.type = "Elf";
Elf.castSpell = function () {
return `${this.type} casts a shimmering light spell.`;
};
const HighElf = Object.create(Elf);
HighElf.type = "High Elf";
HighElf.castSpell = function () {
return `${this.type} casts an ancient powerful spell.`;
};
console.log(Creature.describe());
console.log(Elf.describe());
console.log(Elf.castSpell());
console.log(HighElf.describe());
console.log(HighElf.castSpell());
Here, Elf
inherits from Creature
, and HighElf
inherits from Elf
. Each level can override methods like castSpell
to create unique behavior. This example shows how prototypical inheritance can model real-world hierarchies with shared and specialized behaviors.
Conclusion
Prototypical inheritance is the foundation of JavaScript’s object model, allowing objects to inherit properties and methods directly from other objects. Using Object.create()
, constructor functions, and prototype chains, you can set up inheritance that shares functionality efficiently while allowing overrides and extensions. Understanding this model helps unlock the full power of JavaScript objects and their dynamic behavior.
By practicing with real examples, such as creating vehicles, animals, or magical creatures, you see prototypical inheritance in action and appreciate its flexibility and expressiveness.
References
If you want to learn more about JavaScript prototypical inheritance and related topics, these resources are excellent starting points:
- MDN Web Docs: Inheritance and the prototype chain
The official documentation explaining inheritance and how prototypes chain together. - MDN Web Docs: Object.create()
Learn aboutObject.create()
and how it sets up prototypes. - MDN Web Docs: Constructor functions
A guide to creating objects using constructor functions and prototypes. - JavaScript.info: Prototypal inheritance
A detailed tutorial on prototypes and inheritance in JavaScript.