Prototypal inheritance in JavaScript: A beginner’s introduction

Oladipupo Ishola
8 min readApr 15, 2023
image by Alex Devero

JavaScript is a powerful programming language that enables developers to build dynamic and interactive web applications. One of JavaScript's key features is its ability to create object-oriented code through prototypal inheritance.

In JavaScript, prototypal inheritance is a method of sharing functionality between objects. It enables you to create new objects by inheriting the properties and methods of existing ones. Because you can reuse and build upon existing code rather than duplicate it, this approach can help you write more efficient and maintainable code.

In this introductory tutorial to prototypal inheritance in JavaScript, we'll look at the fundamentals of objects and prototypes, the role of prototypal inheritance in JavaScript, and the practical applications of this powerful concept. By the end of this guide, you'll have a firm grasp on prototypal inheritance and how to use it to improve your JavaScript code.

II. Understanding Objects and Prototypes

Objects in JavaScript are collections of key-value pairs, where each key represents an object property and each value represents the value of that property. Objects in JavaScript can be created using object literals, constructor functions, or the Object.create() method.

Prototypes, on the other hand, are a method of connecting objects in a hierarchical chain. Every JavaScript object has a prototype, which can be accessed via the proto property. When you access a property on an object, JavaScript first searches the object for that property. If it can't find the property, it looks for it on the prototype of the object. If it still can't find the property, it goes back to the prototype's prototype, and so on until it reaches the end of the prototype chain.

Here's an example of how to create an object and then access its prototype:

// Creating an object
const person = {
name: 'John',
age: 30,
};

// Accessing the object's prototype
console.log(person.__proto__); // Outputs: Object.prototype

In this example, we use an object literal to create an object called "person." The prototype of the object can then be accessed using the proto property, which in this case is Object.prototype.

Next, we'll look at how to use constructor functions to create objects and prototypes:

// Constructor function for creating a Person object
function Person(name, age) {
this.name = name;
this.age = age;
}

// Creating a new Person object
const john = new Person('John', 30);

// Accessing the Person prototype
console.log(john.__proto__); // Outputs: Person.prototype

In this example, we define the "Person" constructor function, which creates a new object with the "name" and "age" properties. Using the "new" keyword, we then create a new “Person” object called John. Finally, we can access the prototype of the 'Person' object by using the proto property, which in this case is Person.prototype.

This is only a basic introduction to JavaScript objects and prototypes. In the following section, we'll look more closely at how inheritance works in JavaScript with prototypes.

III. Inheritance in JavaScript

In JavaScript, inheritance is accomplished by using prototypes. When you create a new object, you can use the Object.create() method to specify its prototype. This creates a new object that inherits all of the specified prototype's properties and methods.

Here’s an example of creating an object that inherits from another object:

// Creating a prototype object
const animal = {
walk() {
console.log('Walking...');
}
};

// Creating a new object that inherits from animal
const dog = Object.create(animal);

// Accessing a property on the dog object
dog.walk(); // Outputs: "Walking..."

In this example, we make an "animal" prototype object with a "walk" method. Using the Object.create() method, we then create a new object called "dog" that inherits from 'animal'. Finally, we invoke the "walk" method on the "dog" object, which prints "Walking..." to the console.

Constructor functions can also be used to create objects that inherit from other objects. As an example, consider the following:

// Constructor function for creating a Person object
function Person(name, age) {
this.name = name;
this.age = age;
}

// Adding a method to the Person prototype
Person.prototype.walk = function() {
console.log(`${this.name} is walking...`);
}

// Constructor function for creating a Student object
function Student(name, age, major) {
Person.call(this, name, age);
this.major = major;
}

// Creating a new object that inherits from Person
Student.prototype = Object.create(Person.prototype);

// Adding a method to the Student prototype
Student.prototype.study = function() {
console.log(`${this.name} is studying ${this.major}...`);
}

// Creating a new Student object
const john = new Student('John', 20, 'Computer Science');

// Accessing properties and methods on the john object
john.walk(); // Outputs: "John is walking..."
john.study(); // Outputs: "John is studying Computer Science..."

In this example, we define a constructor function called "Person" that, on its prototype, creates a new object with the properties "name" and "age," as well as a “walk” method. We then define a constructor function called 'Student' that uses the Object.create() method to create a new object with the 'major' property and inherits from 'Person'. On the “Student” prototype, we also define a 'study' method.

Finally, we create a new 'Student' object called 'john' and use its 'walk' and 'study' methods. The 'Person' prototype calls the 'walk' method, while the 'Student' prototype calls the 'study' method.

In JavaScript, inheritance can be a powerful tool for writing efficient and maintainable code. In the following section, we'll look at some real-world examples of inheritance in JavaScript.

IV. The Prototype Chain

Objects in JavaScript inherit properties and methods from their prototypes. If a property or method is not found on an object, JavaScript searches for it on the object's prototype. If it is not found on the prototype, JavaScript searches the prototype chain until it reaches the 'Object' prototype, which is the chain's final link.

Here’s an example:

// Creating a prototype object
const animal = {
walk() {
console.log('Walking...');
}
};

// Creating a new object that inherits from animal
const dog = Object.create(animal);

// Adding a bark method to the dog object
dog.bark = function() {
console.log('Barking...');
};

// Accessing properties and methods on the dog object
dog.walk(); // Outputs: "Walking..."
dog.bark(); // Outputs: "Barking..."

// Checking the prototype chain
console.log(Object.getPrototypeOf(dog)); // Outputs: "animal"
console.log(Object.getPrototypeOf(animal)); // Outputs: "Object.prototype"
console.log(Object.getPrototypeOf(Object.prototype)); // Outputs: "null"

In this case, we'll create an "animal" prototype object with a "walk" method. Using the Object.create() method, we then create a new object called "dog" that inherits from 'animal'. We extend the "dog" object with a 'bark' method.

When we call the "walk" method on the "dog" object, JavaScript looks for it first on the "dog" object, but if it isn't found there, it looks for it on the 'animal' prototype, where it is found and executed.

When we invoke the "bark" method on the "dog" object, JavaScript locates and executes it directly on the "dog" object.

We can also use the Object.getPrototypeOf() method to inspect the prototype chain. In this example, we can see that the prototype of the "dog" object is 'animal,' and the prototype of “animal” is Object.prototype, which is the final link in the chain.

Understanding the prototype chain is essential when working with JavaScript prototypes and inheritance. In the following section, we will look at some common errors and how to avoid them.

VI. Common Mistakes and How to Avoid Them

  1. Modifying a Prototype Object Directly

When working with prototypes in JavaScript, one common mistake is modifying the prototype object directly, which can have unintended consequences. Consider the following example:

function Person(name) {
this.name = name;
}

Person.prototype.greet = function() {
console.log(`Hi, my name is ${this.name}`);
};

const john = new Person('John');

// Modifying the prototype object directly
Person.prototype.sayAge = function() {
console.log(`I am ${this.age} years old`);
};

john.age = 30;
john.sayAge(); // Outputs: "I am 30 years old"

In this example, we define a 'Person' constructor function and attach a 'greet' method to its prototype. We then use the 'Person' constructor to create a new 'john' object.

We then make the error of directly modifying the 'Person' prototype object by adding a 'sayAge' method. The 'age' property is then added to the 'john' object, and the' sayAge' method is called on it.

While this works, it is not recommended to directly modify prototype objects. We should instead use inheritance to create new objects that inherit from the prototype object. We can accomplish this by calling the Object.create() method.

2. Shadowing Properties

Shadowing properties is another common error when working with prototypes. This happens when a property on an object is defined that already exists on the object's prototype. Let’s look at an example:

function Animal(name) {
this.name = name;
}

Animal.prototype.speak = function() {
console.log(`The ${this.constructor.name} speaks`);
};

function Dog(name) {
this.name = name;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// Shadowing the speak method
Dog.prototype.speak = function() {
console.log(`${this.name} barks`);
};

const dog = new Dog('Buddy');
dog.speak(); // Outputs: "Buddy barks"

We have an 'Animal' constructor function with a'speak' method defined on its prototype in this example. 'Object.create()' is then used to create a "Dog" constructor function that inherits from "Animal."

Then we make the mistake of defining a "speak" method on the "Dog" prototype, which shadows the 'Animal' prototype's "speak" method.

When we invoke the "speak" method on the "dog" object, JavaScript locates and executes the "speak" method on the "Dog" prototype, producing the output "Buddy barks."

To avoid shadowing properties, we can either use a different property name or the 'super' keyword to invoke the parent constructor's method.

By understanding these common mistakes and how to avoid them, we can work more effectively with prototypes and inheritance in JavaScript.

VII. Practical Applications

  1. Sharing Functionality Between Objects Prototypal inheritance can be used to share functionality between objects. By defining methods on an object’s prototype, we can create a hierarchy of objects that inherit these methods. Let’s look at an example:
function Shape() {}

Shape.prototype.area = function() {
console.log('Area of shape is undefined');
};

function Rectangle(width, height) {
this.width = width;
this.height = height;
}

Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

Rectangle.prototype.area = function() {
return this.width * this.height;
};

const rect = new Rectangle(5, 10);
console.log(rect.area()); // Outputs: 50

In this example, we have a 'Shape' constructor function with an 'area' method defined on its prototype. We then use Object.create() to create a 'Rectangle' constructor function that inherits from 'Shape'.

Then, on the 'Rectangle' prototype, we define an 'area' method to compute the rectangle's area.

When we use the "Rectangle" constructor to create a new "rect" object and call its "area" method, JavaScript first looks for the "area" method on the "Rectangle" object. It runs it and returns the area of the rectangle once it has been found.

2. Creating Custom Objects

To create specialized custom objects, prototypal inheritance can also be used. Consider the following example:

function Animal(name) {
this.name = name;
}

Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise`);
};

function Dog(name) {
Animal.call(this, name);
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.speak = function() {
console.log(`${this.name} barks`);
};

const dog = new Dog('Buddy');
dog.speak(); // Outputs: "Buddy barks"

In this example, we define a 'speak' method on the prototype of an 'Animal' constructor function. 'Object.create()' is then used to create a 'Dog' constructor function that inherits from 'Animal'.

Then we define a 'speak' method on the 'Dog' prototype that overrides the 'speak' method on the 'Animal' prototype.

When we create a new "dog" object from the "Dog" constructor and call its "speak" method, JavaScript finds and executes the "speak" method on the "Dog" prototype, producing the output "Buddy barks."

Using prototypal inheritance, we can create custom objects with specialized behavior that can be reused throughout our codebase.

VIII. Conclusion

Prototypal inheritance is a powerful concept in JavaScript that allows developers to create flexible and reusable code. Using prototypes to define object behavior, we can share functionality between objects and create custom objects with specialized behavior.

In this article, we covered the fundamentals of JavaScript objects and prototypes, how inheritance works in JavaScript, and how the prototype chain works. We also looked at some common prototype chain mistakes and how to avoid them, as well as practical applications of prototypal inheritance.

Understanding prototypal inheritance is essential for any JavaScript developer seeking to write clean, efficient, and reusable code. By mastering these concepts, you can unlock the full potential of JavaScript and advance your skills.

--

--

Oladipupo Ishola

Tech Writer | Mentor & Code Instructor | Bootcamp Grad | Full Stack Developer | Frontend Developer | Backend Developer | MLH ’21 Alum