JavaScript has prototype mechanism that enables different objects to have hierarchical relationships, share data and functionality. This capability is often called prototype inheritance.

It’s basic use is that if we can’t find some property on an object, we look for it on prototype (another object) if there is one.

Bad naming?

Naming causes a lot of confusion trying to explain and understand prototype inheritance in JavaScript. There are constructor property, class keyword, instanceof operator that may not always be or do what you expect. Most importantly we find two “prototype” properties here: [[Prototype]] and prototype. They are not the same.

Here when talking about the actual object’s prototype from which one inherits functionality and data we will use [[Prototype]] as it is referred in ECMA specification

So what concepts and mechanisms are involved in JavaScript inheritance?

[[Prototype]]

It is object’s internal property pointing to the actual object’s prototype. Almost all objects get it when created except objects created with null as it’s prototype i.e. Object.create(null).

We can access it with Object.getPrototypeOf(), Object.setPrototypeOf or with __proto__* property.

[[Prototype]] property points to another object from which object with the property inherits properties and methods. That means that if we can’t find a property on an object we look for it on it’s [[Prototype]].

* __proto__ is deprecated and not recommended but still is implemented in many browsers

Example

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

Star.prototype.logName = function() {
 console.log(this.name);
};

const kim = new Star('Kim');

console.dir(kim.hasOwnProperty('logName')); // false (there is no logName property on the object)
kim.logName(); // logs the name

Here we see that object kim has no logName method but it can use it because it is found in it’s [[Prototype]]. It might not be clear how we created this relationship but it will be explained later.

[[Prototype]] also explains why many built-in methods work on certain data types. For example built-in Array object inherits properties and methods from Array object’s [[Prototype]], that’s why arrays have methods like map().
You may ask how string, number, and boolean data types which are not objects can have methods. They have their object equivalents that are used as wrappers in those situations and disappear right after.

prototype property

This is another property that is found in functions. Functions* that can be used in constructor calls (with new operator) including functions defined with class keyword get this non-enumerable property that points to an object. To what object does it point?

It is not pointing to that functions [[Prototype]]!

It points to the same object as the [[Prototype]] of the object you create using that function with new keyword.

That may sound confusing, let’s see an example.

* Not all functions have prototype property including some JavaScript built-in functions, static functions defined in classes, and arrow functions. Functions returned with bind() don’t get a prototype property but can be used in a constructor call (with new keyword) and instanceof checks (explained further) using it’s target functions prototype property

Example

function Star(name) { // function with prototype property
  this.name = name;
};

const kim = new Star('Kim'); // function called with new keyword

console.log(Star.prototype.isPrototypeOf(kim)); // true

We see here that Star functions prototype property is the same object as the newly created kim object’s [[Prototype]].

If we want objects created with constructor calls on these functions to get certain functionality, we can add it on the function’s prototype property.

Example

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

Person.prototype.logName = function() { // add logName to Person.prototype
  console.log(this.name);
}

const timmy = new Person('Timmy');
timmy.logName(); // Timmy

We added logName to Person.prototype so now [[Prototype]] of timmy has it and timmy can use it by accessing properties on it’s prototype chain.

It is common to keep private stuff in a function’s used for constructor calls body (name in this example) and reusable, common data and functionality in that function’s prototype property object (as the logName function in this case).

Prototype chain

Object’s [[Prototype]] may have it’s own [[Prototype]] and that object might as well have one. The entirety of prototypically linked objects from the top object to the bottom is called prototype chain.

Normally it ends with Object.prototype which has null as it’s prototype, though it is changeable.

Let’s create a chain. Here we will use __proto__ getter to get the [[Prototypes]] of objects because it is chainable and makes it easy to demonstrate the prototype chain.

Example

const obj = {
  desc: 'I am the top Object',
};

const proto = {
  desc: 'I am [[Prototype]] of obj',
}

Object.setPrototypeOf(obj, proto);

console.dir(obj.desc); // I am the top Object
console.dir(obj.__proto__.desc); // I am [[Prototype]] of obj
console.dir(obj.__proto__.__proto__ === Object.prototype); // true
console.dir(obj.__proto__.__proto__.__proto__ === null); // true

Our prototype chain in the example is:

  1. obj is the top object starting the chain.
  2. proto object is second in line It is the [[Prototype]] of the obj.
  3. Object.prototype is third. It is the (default) [[Prototype]] of proto.

Object.prototype ends the chain because it has null as it’s [[Prototype]].

If we cannot find a certain property on the top object we go down to it’s [[Prototype]], if it’s not there we go down. That’s the usual behavior though there are more rules we are not going to discuss here that determine the behavior of object property retrieval and especially setting.

constructor property

Object found in function’s prototype property gets a constructor property. It is meant to point back to the function which was used to create the object with a constructor* call.

* Many people call functions that can be called with the new operator ‘constructor functions’ or these calls ‘constructor calls’ as I just did but that and this property is not the same thing even though it may often point to one such function

Example

function Person() {};

const person = new Person();
const obj = {};
const arr = [];

console.dir(person.constructor); // Person() function
console.dir(obj.constructor); // Object() function
console.dir(arr.constructor); // Array() function

Here we see that created objects have access to constructor property that points to the function used to create the object with new*. This property in this case is found on their [[Prototypes]] and it works out fine though it is quite easy to lose this capability.

* {} and [] are equivalents for new Object() and new Array()

Example

function Person() {};
Person.prototype = {};

const person = new Person();

console.dir(person.constructor); // Object() function

In this example we cannot find a constructor property on the [[Prototype]] of person object so we look for it on person’s [[Prototype’s]] [[Prototype]] and find it. This time we are not getting what we are looking for – Object() was not used to create person.

Another example

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

Animal.prototype.logType = function() {
  console.log(this.type);
}

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

Dog.prototype = Object.create(Animal.prototype); // we create a new Dog.prototype (constructor property is gone)
Dog.prototype.bark = function() {
  console.log('woof!');
}

const savana = new Dog('Savana');
savana.logType();  // dog
savana.bark(); // woof

console.log(savana.constructor); // logs Animal function

This example is kinda big and we will come back to it but for now the point is that we created a new object for Dog.prototype which had it’s [[Prototype]] set to Animal.prototype and replaced the original object which had the constructor property. Now when we are looking for that property on object savana we find it not on savana’s [[Prototype]] but deeper in [[Prototype’s]] [[Prototype]] and get Animal function which is not what were looking for (considering the naming of the property).

So be careful when using constructor property.

Sharing [[Prototype]]

It is important to undestand that objects found in the prototype chain are linked and not copied and if you modify it in one place, it affects other places as well.

Example

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

Person.prototype.logName = function() {
  console.log(this.name);
}

const timmy = new Person('Timmy');
const bimmy = new Person('Bimmy');

Object.getPrototypeOf(timmy).logName = function() {
  console.log('changed');
}

timmy.logName(); // changed
bimmy.logName(); // changed

We changed logName function on the [[Prototype]] of timmy and we can see that logName function changed for bimmy as well. This is because timmy and bimmy were sharing the same object referenced by their [[Prototype]] properties. Be cautious of this as lack of understanding here can lead to unexpected behavior.

Ways to inspect prototype relationships

These are the tools you can use for finding [[Prototypes]] and evaluating prototypical relationships between objects and functions.

1. instanceof

formula: obj instanceof Func;

Show if in obj’s prototype chain Func.prototype appear.

Example

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

const timmy = new Person('Timmy');

console.log(timmy instanceof Person); // true

You need a function capable of constructor calls and an object to test this one.

2. isPrototypeOf()

formula: obj1.isPrototypeOf(obj2);

Here we just need two objects to check if one is the prototype chain of another.

Example

const animal = {};
const dog = {};
const butkus = {};

Object.setPrototypeOf(dog, animal);
Object.setPrototypeOf(butkus, dog);

console.log(animal.isPrototypeOf(butkus)); // true

The object animal is not direct [[Prototype]] of butkus but is in it’s prototype chain.

3. Object.getPrototypeOf();

formula: Object.getPrototypeOf(obj);

Retrieves the [[Prototype]] of obj.

4. __proto__

formula: obj.__proto__

Retrieves [[Prototype]] of obj. Can be chained (i.e. obj.__proto__.__proto__). As mentioned before it is deprecated and not recommended though still supported in many browsers.

__proto__ is an accessor property (a getter and a setter functions) that exist on Object.prototype (the bottom object of the usual prototype chain) and is not found on the top object itself.

Example

const emptyPrototype = Object.create(null);

const obj = Object.create(emptyPrototype);

console.dir(Object.getPrototypeOf(obj) === emptyPrototype); // true
console.dir(obj.__proto__ === emptyPrototype); // false

We get false in the second log because __proto__ is undefined, because we have no access to Object.prototype on which it is normally found, because our [[Prototype]] is empty and does not have Object.prototype as it’s [[Prototype]]. We can still use Object.getPrototypeOf().

5. for…in loop

for…in loop walks the prototype chain and gets all enumerable, non-Symbol properties on it.

Example

const proto = {
  protoItem: 'someValue',
}

const obj = Object.create(proto);
obj.objItem = 'someValue';

for (let prop in obj) {
  console.log(prop);
}

In the example the loop logs protoItem and objItem. It is an easy way to go through properties by logging them for debugging purposes.

6. in operator

formula: ‘propertyName’ in obj

The in operator is used for checking if a property is in object’s prototype chain regardless of enumerability.

Example

console.dir('propertyIsEnumerable' in {}); // true

The example logs true bacause propertyIsEnumerable property is found in Object.prototype which is the [[Prototype]] of the object created with object literal syntax “{}”.

Creating prototype relationships

We can separate two ways for creating prototypical relationships. When a new object is created and gets some kind of a [[Prototype]] and when a [[Prototype]] of an existing object is changed.

1. Creating a new object

Almost always when you create an object it gets some [[Prototype]] object. There are two different ways you can create an object based on what [[Prototype]] it will get.

i. Object.create()*

formula: const obj = Object.create(desiredPrototype);

We can create and object with desired [[Prototype]] this way.

Example

const star = { // our desired [[Prototype]]
  logName: function() {
    console.log(this.name);
  }
}

// create an object with star as prototype
const kim = Object.create(star);
kim.name = 'Kim';

console.dir(Object.getPrototypeOf(kim) === star); // true (star is [[Prototype]] of kim)
kim.logName(); // property found in [[Prototype]] works!

This is simple and useful solution for implementing prototype inheritance.

* Object.create() was introduced in ES5 and by default won’t work in older JS environments.

Another example

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

Animal.prototype.logType = function() {
  console.log(this.type);
}

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

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

Dog.prototype.bark = function() {
  console.log('woof!');
}

const savana = new Dog('Savana');
savana.logType(); // dog
savana.bark(); // woof

This is an example we used before but now we are going to look at it in another way. It shows a more complex, deeper relationship possible in JavaScript.

We should focus on this line:
Dog.prototype = Object.create(Animal.prototype).

Here if we want the object savana to have access to both Dog.prototype and Animal.prototype properties we need to connect them. We do so by setting Dog.prototype to be an object which has a [[Prototype]] link to Animal.prototype. The old Dog.prototype is replaced by the new object.
It is possible to achieve this kind of a relationship with Dog.prototype = new Animal() instead of the original code as well, but then we would have to enjoy the fruits of the function’s side effects (setting of this.type in this case).
Another way to achieve that could be using Object.setPrototypeOf() (explained further).

No [[Prototype]]?

Using Object.create(null) we can create an object with no [[Prototype]] object at all. With no additional functionality down the prototype chain we easily know what to expect from it. That could be useful for storing data, using it like a dictionary data structure.

Example

const dictionary = Object.create(null);

console.log(Object.getPrototypeOf(dictionary)); // null
console.dir(dictionary); // logs empty object

ii. using new operator

formula: const obj = new Func();

We can create an object with a specific [[Prototype]] when calling a function with new operator. As mentioned before it gets it’s [[Prototype]] set to the object found on that function’s prototype property.

Objects created using object or array literal syntax ({…} and […]) get the same [[Prototype]] as calling new Array() or new Object(). Function and class definitions get the same [[Prototype]] as calling new Function().

We can also achieve the same result using Reflect.construct() as using the new operator.

Example

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

Star.prototype.logName = function() {
  console.log(this.name);
}

// create an object with it's [[Prototype]] as Star.prototype
const kim = new Star('Kim');

kim.logName(); // Kim

2. Setting a new [[Prototype]] on an existing object

There are also two ways you can set a [[Prototype]] on an existing object.

Altering prototypes is a slow operation and often if you care about performance it is better to create a new object with the desired prototype, though I did not notice any difference when testing these myself on a small scale. Be the judge of your situation.

i. Object.setPrototypeOf()

formula: Object.setPrototypeOf(obj, desiredPrototype);

A modern ES6 solution

Example

const kim = {
  name: 'Kim'
};
const star = {
  logName: function() {
    console.log(this.name);
  }
}

Object.setPrototypeOf(kim, star);

kim.logName(); // Kim

ii. using __proto__

formula: obj.__proto__ = desiredPrototype;

Reminder: __proto__ property pointing to the [[Prototype]] is considered legacy though still widely supported.

Example

const kim = {
  name: 'Kim'
};

const star = {
  logName: function() {
    console.log(this.name);
  }
}

kim.__proto__ = star;

kim.logName(); // Kim

Difference from other languages

Inheritance in JavaScript may seem unusual comparing to other popular programming languages that use class-based inheritance (i.e. PHP, Java) and not prototypical inheritance.

Class based inheritance is based on classes (obviously). One may argue than in JavaScript there are no classes per se. Since ES6 we can find “syntactical sugar” that looks similar to class-based inheritance implementation (class, extends keywords, etc.) but at it’s core we are still using dynamic, mutable objects, that reference other objects and in the same manner as they did before. Even if we see a “class” in JavaScript, it is also a function and an object at the same time.

Summary

Prototype mechanism in JavaScript is an important feature of the language and even though it might not be easy to understand it is unavoidable. My recommendation is to practice creating and inspecting various patterns using it and also inspecting JavaScript’s built-in objects and functionality.

Best Wishes, Vladas Končius


Leave a Reply

Your email address will not be published.