Abstract

This is tutorial text on “prototypal inheritance” in JavaScript. Some theory is explained, and several JavaScript examples are shown, together with screenshots of “internals” from the DevTools debugger.

1. Introduction

Developers coming from some of the modern Object-Oriented languages like C++, Java, and C# to JavaScript find often JavaScript “dirty”, obscure, and full or archaic language expressions that are kept in language for historical backward compatibility. One thing in particular that you will find different is the “object inheritance model” in JavaScript which looks similar to the above OO languages but is inside radically different. While in modern languages like C++, Java, and C# the object inheritance model is “class-based”, in JavaScript is “instance-based”. This text is not meant to be a complete tutorial, but rather a clarification paper that presents a sufficient number of examples and screenshots from the Chrome DevTools debugger to clarify how inheritance is implemented internally. The intended audience is Intermediate JavaScript developers and above.

2. Theoretical background

Before some practical examples, some theoretical background is needed. We are focused on explaining and comprehending the basic concepts, leaving more details to further reading of references.

2.1. Simplified definitions

Here are some plain language explanations.

What is “class-based inheritance”?

Class-based inheritance is a paradigm/manner of object-oriented programming in which the reuse of functionality/code is done by a process of reusing existing classes. Here a “class” is a template for creating objects. Class is an abstract structure, rather than a specific instance. Some of the programming languages that use this paradigm are C++, Java, and C#.

What is “prototypal inheritance”?

Prototypal inheritance is a paradigm/manner of object-oriented programming in which the reuse of functionality/code is done by a process of reusing existing objects that serve as prototypes for new objects. A prototype object is not an abstract structure, but rather a specific instance. One of the programming languages that use this paradigm is JavaScript.

What is a “prototype object” in “prototypal inheritance”?

A prototype object is an instance of an object whose state and behavior are about to be reused in the process of “prototypal inheritance”.

Which is better, “prototypal inheritance” or “class-based inheritance?

Prototypal inheritance” was popular in the 1980s. Since the late 1990s, the “class-based inheritance” paradigm has become more popular.

2.2. Prototypal Inheritance in JavaScript

Here are some simplified explanations.

2.3. Examples

The examples below are for a developer who already has some experience with JavaScript. The focus is more on the internals of how JavaScript internally represents objects, than on presenting/teaching JavaScript here. We demo the creation of objects in 3 different ways:

  1. Literal syntax
  2. Constructor function syntax
  3. Class syntax

These are not the only ways to create objects in JavaScript, but they present sufficient variety for this article. We used Chrome DevTools debugger to have a look into how objects and inheritance are represented internally. The source code of all examples is attached. Examples were executed on Chrome Version 116.0.5845.188 (Official Build) (64-bit).

3. Example01 Object - Literal syntax

3.1. Example code

Please consider the following example.

//Example 01
//Object - Literal syntax

let person1 = {
    name: "Mark",
    age: 21,
}
<!--
Output
Accessing object property:
person1.name:Mark
Calling method inherited from the prototype:
person1.toString():[object Object]
Accessing object prototype:
person1.__proto__.toString():[object Object]
person1.__proto__.constructor.name:Object
Checking if we are at the root of the inheritance:
person1.__proto__.__proto__:null
    -->

Here is the execution screen.

Example 1

3.2. DevTools screenshots

Here are some screenshots of Chrome DevTools during execution.

DevTools

3.3. Example comments

4. Example02 Object - Constructor function syntax

4.1. Example code

Please consider the following example.

//Example 02
//Object - Constructor function syntax
function Person(name, age) {
    this.name = name;
    this.age = age;
}
let person1 = new Person("Mark", 21);
<!--
Output
Accessing object property:
person1.name:Mark
Calling method inherited from the prototype:
person1.toString():[object Object]
Checking object class:
person1 instanceof Person:true
Accessing object prototype:
person1.__proto__.toString():[object Object]
person1.__proto__.constructor.name:Person
Accessing object prototype’s prototype:
person1.__proto__.__proto__:[object Object]
person1.__proto__.__proto__.constructor.name:Object
Checking if we are at the root of the inheritance:
person1.__proto__.__proto__.__proto__:null
    -->

Here is the execution screen.

Example 2

4.2. DevTools screenshots

Here are some screenshots of Chrome DevTools during execution.

Chrome DevTools

4.3. Example comments

5. Example03 Object - Class syntax

5.1. Example code

Please consider the following example.

//Example 03
//Object - Class syntax

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

let person1 = new Person("Mark", 21);

<!--
Output
Accessing object property:
person1.name:Mark
Calling method inherited from the prototype:
person1.toString():[object Object]

Checking object class:
person1 instanceof Person:true

Accessing object prototype:
person1.__proto__.toString():[object Object]
person1.__proto__.constructor.name:Person

Accessing object prototype’s prototype:
person1.__proto__.__proto__:[object Object]
person1.__proto__.__proto__.constructor.name:Object

Checking if we are at the root of the inheritance:
person1.__proto__.__proto__.__proto__:null
    -->

Here is the execution screen.

Example 3

5.2. DevTools screenshots

Here are some screenshots of Chrome DevTools during execution.

Class person

5.3. Example comments

6. Example04 Multiple Objects - Literal syntax

6.1 Example code

Please consider the following example.

//Example 04
//Multiple Objects - Literal syntax
let person1 = {
    name: "Mark",
    age: 21,
    toString: function () {
        return `Person ${this.name}, old ${this.age} (ver1)`;
    },
}
let person2 = {
    name: "Novak",
    age: 36,
    toString: function () {
        return `Person ${this.name}, old ${this.age} (ver2)`;
    },
}
person1.city = "Belgrade";
person2.profession = "programmer";
person1.__proto__.country = "Serbia";
<!--
Output
Accessing person1:
person1.name:Mark
person1.city:Belgrade
person1.profession:undefined
person1.country:Serbia
person1.toString():Person Mark, old 21 (ver1)
Accessing person2:
person2.name:Novak
person2.city:undefined
person2.profession:programmer
person2.country:Serbia
person2.toString():Person Novak, old 36 (ver2)
    -->

Here is the execution screen.

Example 4

6.2. DevTools screenshots

Here are some screenshots of Chrome DevTools during execution.

String

Chrome tool

6.3. Example comments

7. Example05 Multiple Objects - Constructor function syntax

7.1. Example code

Please consider the following example.

//Example 05
//Multiple Objects - Constructor function syntax
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.toString = function () {
        return `Person ${this.name}, old ${this.age}`;
    }
}
let person1 = new Person("Mark", 21);
let person2 = new Person("Novak", 36);
person1.city = "Belgrade";
person2.profession = "programmer";
person1.__proto__.country = "Serbia";
<!--
Output
Accessing person1:
person1.name:Mark
person1.city:Belgrade
person1.profession:undefined
person1.country:Serbia
person1.toString():Person Mark, old 21
Accessing person2:
person2.name:Novak
person2.city:undefined
person2.profession:programmer
person2.country:Serbia
person2.toString():Person Novak, old 36
    -->

Here is the execution screen.

Example 5

7.2. DevTools screenshots

Here are some screenshots of Chrome DevTools during execution.

Prototype object

Constructor

7.3. Example comments

8. Example06 Multiple Objects - Class syntax

8.1. Example code

Please consider the following example.

//Example 06
//Multiple Objects - Class syntax
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    toString() {
            return `Person ${this.name}, old ${this.age}`;
    }
}
let person1 = new Person("Mark", 21);
let person2 = new Person("Novak", 36);
person1.city = "Belgrade";
person2.profession = "programmer";
person1.__proto__.country = "Serbia";
<!--
Output
Accessing person1:
person1.name:Mark
person1.city:Belgrade
person1.profession:undefined
person1.country:Serbia
person1.toString():Person Mark, old 21
Accessing person2:
person2.name:Novak
person2.city:undefined
person2.profession:programmer
person2.country:Serbia
person2.toString():Person Novak, old 36
    -->

Here is the execution screen.

Example 6

8.2. DevTools screenshots

Here are some screenshots of Chrome DevTools during execution.

Devtool

String

8.3. Example comments

9. Example07 Object Inheritance - Literal syntax

9.1. Example code

Please consider the following example.

//Example 07
//Object Inheritance - Literal syntax
let person1 = {
    name: "Mark",
    age: 21,
    toString: function () {
        return `Person ${this.name}, old ${this.age} (ver1)`;
    },
}
let student1 = {
    course: "Computers",
}
student1.__proto__ = person1;
<!--
Output
Accessing object properties and methods:
student1.name:Mark
student1.course:Computers
student1.toString():Person Mark, old 21 (ver1)
Accessing object prototype:
student1.__proto__.toString():Person Mark, old 21 (ver1)
student1.__proto__.constructor.name:Object
Accessing object prototype’s prototype:
student1.__proto__.__proto__.toString():[object Object]
student1.__proto__.__proto__.constructor.name:Object
Checking if we are at the root of the inheritance:
student1.__proto__.__proto__.__proto__:null
    -->

Here is the execution screen.

Example 7

9.2. DevTools screenshots

Here are some screenshots of Chrome DevTools during execution.

Student

9.3. Example comments

10. Example08 Object Inheritance- Constructor function syntax

10.1. Example code

Please consider the following example.

//Example 08
//Object Inheritance- Constructor function syntax
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.toString = function () {
        return `Person ${this.name}, old ${this.age}`;
    };
}
let person1 = new Person("Mark", 21);
function Student(course) {
    this.course = course;
}
Student.prototype = person1;
let student1 = new Student("Computers");
<!--
Output
Accessing object properties and methods:
student1.name:Mark
student1.course:Computers
student1.toString():Person Mark, old 21
Checking object class:
student1 instanceof Person:true
student1 instanceof Student:true
Accessing object prototype:
student1.__proto__.toString():Person Mark, old 21
student1.__proto__.constructor.name:Person
Accessing object prototype’s prototype:
student1.__proto__.__proto__.toString():[object Object]
student1.__proto__.__proto__.constructor.name:Person
Accessing object prototype’s prototype’s prototype:
student1.__proto__.__proto__.__proto__.toString():[object Object]
student1.__proto__.__proto__.__proto__.constructor.name:Object
Checking if we are at the root of the inheritance:
student1.__proto__.__proto__.__proto__.__proto__:null
    -->	

Here is the execution screen.

Example 8

10.2. DevTools screenshots

Here are some screenshots of Chrome DevTools during execution.

Computers

10.3. Example comments

11. Example09 Object Inheritance - Class syntax

11.1. Example code

Please consider the following example.

//Example 09
//Object Inheritance - Class syntax
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    toString() {
        return `Person ${this.name}, old ${this.age}`;
    };
}
class Student extends Person {
    constructor(course, name, age) {
        super(name, age);
        this.course = course;
    }
}
let student1 = new Student("Computers", "Mark", 21);
<!--
Output
Accessing object properties and methods:
student1.name:Mark
student1.course:Computers
student1.toString():Person Mark, old 21
Checking object class:
student1 instanceof Person:true
student1 instanceof Student:true
Accessing object prototype:
student1.__proto__.toString():Person undefined, old undefined
student1.__proto__.constructor.name:Student
Accessing object prototype's prototype:
student1.__proto__.__proto__.toString():Person undefined, old undefined
student1.__proto__.__proto__.constructor.name:Person
Accessing object prototype's prototype's prototype:
student1.__proto__.__proto__.__proto__.toString():[object Object]
student1.__proto__.__proto__.__proto__.constructor.name:Object
Checking if we are at the root of the inheritance:
student1.__proto__.__proto__.__proto__.__proto__:null
    -->	

Here is the execution screen.

Example 9

11.2. DevTools screenshots

Here are some screenshots of Chrome DevTools during execution.

Class person

11.3. Example comments

12. Conclusion

We showed in number of examples in this text how “prototypical inheritance” in JavaScript works. This is by no means exhaustive text on the topic, objects can be created in different ways and there is more JavaScript material on the topic to be covered. But, our goals in this text were to focus on and show some basic principles and outline the internals of how JavaScript prototypical inheritance works.

13. References

  1. https://en.wikipedia.org/wiki/Prototype-based_programming
  2. https://en.wikipedia.org/wiki/Class-based_programming
  3. https://javascript.info/
  4. David Flanagan, JavaScript: The Definitive Guide, Seventh Edition, O’Reilly 2020.