DEV Community

Cover image for Prototypal inheritance; what really happens under the hood.
Joshua Ng'ang'a Raphael
Joshua Ng'ang'a Raphael

Posted on

Prototypal inheritance; what really happens under the hood.

Everything behaves as an object
In JavaScript, arrays and functions and various datatypes are considered to be objects or rather behave like one.
An object is defined as a composite data type representing a collection of related data and functionality .

They mainly consist of;

  • Properties: key-value pairs which define the object's characteristics
  • Methods: functions that are associated with the object and operate on its properties.

Functions have built in methods such as 'apply' that calls the function with a given 'this' value and also has properties such as 'length' which would indicate the number of parameters expected by a given function.
One can view in-built methods and properties of a function through the console by logging the following;

// Log built-in methods of a function
console.log(Object.getOwnPropertyNames(Function.prototype));
Enter fullscreen mode Exit fullscreen mode

Console logging of function methods

// Log built-in properties of a function
console.log(Object.getOwnPropertyNames(Function));

Enter fullscreen mode Exit fullscreen mode

Console logging of function properties

Similarly arrays consist of in built methods such as 'push()' that adds elements to the end of an array.
They also have a 'length' property that indicates the number of elements in an array.
One can view an array's in built methods and properties by logging the following

// Log built-in methods of arrays
console.log(Object.getOwnPropertyNames(Array.prototype));
Enter fullscreen mode Exit fullscreen mode

Console logging array methods

// Log built-in properties of arrays
console.log(Object.getOwnPropertyNames(Array));
Enter fullscreen mode Exit fullscreen mode

Console logging array properties

From our earlier definition, it is safe to consider functions and arrays to be objects considering they have in-built key-value pairs (properties) and methods.
For any function or array that is initialized, it inherits this in-built methods from the 'Function.prototype' and the 'Array.prototype'.
What is a prototype?


Prototypal inheritance
A 'Prototype' is an internal property of objects that serves as a blueprint for creating new objects.
Prototypal inheritance allows for the transfer of an objects in built methods and properties to any instantiated object.
When a property or method is accessed on an object, JavaScript will first check if the object has that property or method.
If not it proceeds to move up and looks into the prototype of the object. If the object's prototype does not contain the accessed method or property, JavaScript proceeds to move up once more and check the prototype of our objects' prototype.
This will go on until either the accessed property is found or the ultimate prototype ('Object.prototype') which contains all properties and methods inherited by all objects in JavaScript is reached.
An attempt to access a property above the 'Object.prototype' will return the 'null' object which indicates the property or method an object is trying to access does not exist.
The linking between an object to its prototype from which it inherits its properties and methods to another prototype of another object up until the ultimate prototype 'Object.prototype' is referred to as a 'prototypal chain' and is the mechanism enabling 'prototypal inheritance' which can be defined as the inheritance or the referencing of properties and methods from other objects.

Prototypal chain

Object creation methods and accessing their prototypes

Objects are created using;

  • Object literals
  • Constructor functions
  • 'Object.create()' Each method takes up different syntax and provides varying benefits which we will outline.

Using 'Object literals' (declaring objects)

For creation of simple, single instances of an object with a fixed set of properties and methods, object literals will be our preferred object creation method.

// Creating an object using object literal

var personLiteral = {
  firstName: "Mansa",
  lastName: "Musa",
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
};

console.log(personLiteral.fullName()); // Output: Mansa Musa
Enter fullscreen mode Exit fullscreen mode
  • Getting and setting of an objects prototype used to be carried out using the keyword 'proto' whih has however been deprecated.

In place of 'proto', two new methods provide an explicit way to get and set the prototype of an object;

  • 'Object.setPrototypeOf(obj, prototype)' - sets the prototype of the specified object, in this case 'obj' to the specified prototype 'prototype'.
  • 'Object.getPrototypeOf(obj)' - gets the prototype of our specified object 'obj.

Constructor functions
Constructor functions are used in the creation of objects with multiple instances, prototypal inheritance, and dynamic initialization which allows constructor functions to accept parameters during instantiation.

// Creating objects using constructor function
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;

  this.fullName = function() {
    return this.firstName + " " + this.lastName;
  };
}

// Creating instances of Person
var person1 = new Person("Lucky", "Says");
var person2 = new Person("Bob", "Johnson");

console.log(person1.fullName()); // Output: Lucky  Says
console.log(person2.fullName()); // Output: Bob Johnson

Enter fullscreen mode Exit fullscreen mode

Invoking 'new' instantiates a object from our constructor which inherits the properties of the prototype through a special keyword prototype which copies reference of the prototype object to the new object's internal [[Prototype]] property of the new instance. This effectively shares properties found on the 'prototype object'.
Mutability of prototypes
Objects instantiated using Constructor functions can have their behavior dynamically extended by modifying our object prototypes.

 // Define a constructor function
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// Add a method to the prototype
Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};

// Create instances of Person
var person1 = new Person('Alice', 30);
var person2 = new Person('Bob', 25);

// Call the method added to the prototype
person1.greet(); // Output: Hello, my name is Alice and I am 30 years old.
person2.greet(); // Output: Hello, my name is Bob and I am 25 years old.

// Modifying the prototype to add a new property
Person.prototype.gender = 'Female';

// Accessing the new property from instances
console.log(person1.gender); // Output: Female
console.log(person2.gender); // Output: Female

Enter fullscreen mode Exit fullscreen mode

In this example, We add a method 'greet' to our Person prototype and proceed to create two new instances of Person and call the 'greet' method on them
We then add a new property 'gender' to the Person prototype, essentially adding them to all instances of the object before calling them on our two instances 'person1' and 'person2'.
allowing you to even change properties.

Syntactical sugar
Objects in JavaScript can also be created with classes
**Unlike **other languages working with classes, we can add properties to the prototype and hence to every
instantiated object of this constructor;

class Rectangle {
  constructor(height, width) {
    this.name = "Rectangle";
    this.height = height;
    this.width = width;
  }
}

class FilledRectangle extends Rectangle {
  constructor(height, width, color) {
    super(height, width);
    this.name = "Filled rectangle";
    this.color = color;
  }
}

const filledRectangle = new FilledRectangle(5, 10, "blue");
// filledRectangle ---> FilledRectangle.prototype ---> Rectangle.prototype ---> Object.prototype ---> null


Enter fullscreen mode Exit fullscreen mode

Classes are referred to as 'syntactical sugar' as they merely provide a more declarative and readable syntax for defining object prototypes.
They also provide more understandable syntax for working with inheritance, with built-in keywords like 'extends' for subclassing and 'super' for calling superclass constructors and methods.

Despite the syntactical difference, classes are eventually compiled as constructor functions at runtime.


'Object.create()'
'Object.create()' is a method in JavaScript allowing us to create new objects having specified their prototype.

// Creating a prototype object
var personPrototype = {
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
};

// Creating objects using Object.create()
var person1 = Object.create(personPrototype);
person1.firstName = "Emily";
person1.lastName = "Brown";

var person2 = Object.create(personPrototype);
person2.firstName = "David";
person2.lastName = "Lee";

console.log(person1.fullName()); // Output: Emily Brown
console.log(person2.fullName()); // Output: David Lee
Enter fullscreen mode Exit fullscreen mode

This explicit and direct assigning of prototypes to objects enables us to create objects without the use of constructor functions, thus eliminating any constructor overhead leading to cleaner and more concise code that does not require instantiation-time initialization logic.


Performance concerns
Prototypical inheritance provides great
dynamicity to code and reduces the need for repetitive codebases. It should however be undertaken with care so as to avoid accumulating overhead.
Every attempt to access properties high up on the prototype chain incurs a negative impact on code performance.
To avoid unnecessary performance drops, one may consider checking if the desired property exists on itself using 'hasOwnProperty' or 'Object.hasOwn' before proceeding to traverse the prototype chain.
It is also advisable to check if the desired property exists as trying to access nonexistent properties leads to full traversal of the full prototype chain.

Top comments (0)