Prototypal Inheritance

What is prototypal inheritance in JavaScript?

JavaScript is a prototype-based language which means that each non-null object in JavaScript has a private property __proto__ (aka [[Prototype]] property) which references its prototype. This prototypal inheritance results in the following:

  • When a property of an object is accessed the lookup process starts. First, it checks whether the accessed property is the own property of that object (i.e. whether it is defined directly on that object). If it is not, it looks for that property at its prototype, then at the prototype of its prototype, etc. If such a prototype chain traversal is unsuccessful then undefined is returned.

  • At iteration, every enumerable own property of an object and every enumerable property on that object's prototype chain is enumerated.

const anObj = { a: 1 }
const anObj2 = Object.create(anObj) // Creates an object with anObj as the prototype.
anObj2 // => {}
anObj2.a // => 1
for (const prop in anObj2) { console.log(prop) } // a

const anArr = [1]
const anArr2 = Object.create(anArr) // Creates an object with anArr as the prototype.
anArr2 // => []
anArr2[0] // => 1
anArr2.map(el => el) // => [1]

Object.getPrototypeOf() can be used to get an object's prototype alternatively to accessing __proto__ property.

Own Properties

When a property is defined directly on a given object it is called its own property. To check whether a given property is an own property of an object Object.prototype.hasOwnProperty() can be used.

Prototype Chain

As already noted, all objects in JavaScript have their prototypes referenced by the __proto__ property. __proto__ references create prototypal chain. At the end of all prototypal chains is null.

Prototype Inherited Properties

Non-own properties defined on an object's prototype are accessible to all objects prototyped with that prototype. However, properties set directly on an object have precedence to the properties inherited from prototypal chain.

function Person() {}

const personA = new Person()
const personB = new Person()
personA.surname = 'Doe'
personA.surname // => 'Doe'
personB.surname // => undefined
personA.__proto__.surname = 'Smith'
personA.surname // => 'Doe'
personB.surname // => 'Smith'

Traversing of a prototypal chain of a given object to look for properties may have negative consequences for performance. Further, when no property exists at the object and down its prototypal chain the full traversal needs to be effected to return undefined.

Prototyping

There are several ways of prototyping in JavaScript including:

  • constructor functions,

  • Object.create(),

  • Object.setPrototypeOf(), and

  • manually referencing a prototype in the __proto__ property (considered deprecated).

Constructor Functions

A constructor function (not to be mislead with the Function() constructor) is a function invoked with the new keyword that can serve as a reusable & parameterizable blueprint for creating new objects.

A constructor function uses the keyword this to parametrize objects created with it.

Conventionally a constructor function:

  • should begin with a capital letter, and

  • should not return a value explicitly.

function Animal(species, name) {
  this.species = species
  this.name = name
}

Once a constructor function is declared we can create objects using it as a parameterizable blueprint with the keyword new.

function Animal(species, name) {
  this.species = species
  this.name = name
}

const geraltsHorse = new Animal('horse', 'Roach')
console.log(geraltsHorse) // Animal {species: 'horse', name: 'Roach'}

const cirisHorse = new Animal('horse', 'Kelpie')
console.log(cirisHorse) // Animal {species: 'horse', name: 'Kelpie'}

The steps at creating an object with a constructor function are as follows.

  • A new plain object is being created.

  • The property __proto__ is being added to that newly created object which establishes a reference between that newly created object and the constructor function's prototype property.

  • As the keyword this in the constructor function's body refers to the newly created object the properties are being added to it based on the constructor function's parameters.

  • If the constructor function does not return any value explicitly it returns implicitly the newly created object.

Constructor Function Prototype Property

All objects created with the same constructor function share the same prototype function which is the constructor function's property called prototype. The prototype function is assigned to all objects created with that constructor function and referenced by them using the __proto__ property.

Further, all objects created with a constructor function have a property called constructor which references that constructor function itself.

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

const student = new Person('Michael')
Person.prototype === student.__proto__ // => true
Person === student.constructor // => true
student.__proto__ === student.constructor.prototype // true

The constructor function and its prototype property are shared among all objects created with that constructor function.

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

const studentA = new Person('Michael')
const studentB = new Person('Leo')
studentA === studentB // => false
studentA.constructor === studentB.constructor // => true
studentA.__proto__ === studentB.__proto__ // => true
studentA.__proto__ === studentA.constructor.prototype // => true

All functions but arrow functions in JavaScript have the prototype property.

JavaScript Built-in Constructor Functions

JavaScript has many built in constructor functions including: Function(), Object(), Array(), String(), Number(), Boolean(), RegExp(), and Date(). Note that Math() is not a constructor function.

Strings, numbers and booleans created with constructor functions are not primitives. Generally, primitive alternatives created with literal syntax should be used as simply being faster.

Non-array objects initialized with literal syntax have Object set as their constructors. Array objects initialized with literal syntax have Array set as their constructors.

const anObj = {}
anObj.constructor // => ƒ Object() { [native code] }

const anArr = []
anArr.constructor // => ƒ Array() { [native code] }

Constructor Function Change

It is possible to effectively change a reference to a constructor function but only for objects and not primitives. Changing of a constructor function's reference does not influence instanceof operator.

Creating New Objects with Object's Create Method

Since ES5 it is possible to create a new object with Object.create() method providing it with the an existing object or null as the prototype. The second parameter of Object.create() can be provided to specify property descriptors. Similar effects can be achieved with Object.defineProperties() method.

Setting Prototype

It is possible to change an object prototype using Object.setPrototypeOf().