Summoning Fundamentals: A Three Part Introduction to OOP in JavaScript - II - Prototypical Inheritance

| Comments

The Mastering the Arcane Art of JavaScript-mancy series are my humble attempt at bringing my love for JavaScript to all other C# developers that haven’t yet discovered how awesome this language and its whole ecosystem are. These articles are excerpts of the super duper awesome JavaScript-Mancy book a compendium of all things JavaScript for C# developers.

Thus far you’ve seen how we can achieve encapsulation in JavaScript by using either object initializers or constructor functions with the new operator. You have also learned how to attain data hiding through both closures and ES6 symbols. The next step is inheritance !

Inheritance in C#, either by deriving from an concrete class, an abstract class or by implementing an interface is a mechanism both of code reuse and of polymorphism. JavaScript however, being a dynamic language, achieves polymorphism by other means than inheritance, and keeps inheritance as just a means of code (or behavior) reuse and, in some cases, memory optimization.

Inheritance in JavaScript differs greatly from what you are accustomed to in C#. JavaScript supports a different flavor of inheritance that diverges very much from traditional or class-based inheritance.

How does inheritance work in JavaScript?

JavaScript exhibits what is known as prototypical inheritance where it is an object and not a class the one that acts as blueprint, model or prototype for other objects.

Prototypical inheritance is all about objects being modeled after other objects, no room for classes here. Any object can be based on (or inherit from) a prototype and have additional specific properties and methods that build on top of that original prototype.

Classical Inheritance vs Prototypical Inheritance

At a high level, this is how C# classical inheritance compares to JavaScript prototypical inheritance:

C# Classical Inheritance JavaScript - Prototypical Inheritance
Focuses on classes Focuses on objects
Classes cannot be modified at runtime: You define a class with a series of methods and properties and you cannot add new methods or properties, nor modify the existing ones at runtime. Prototypes are more flexible and extensible than classes. They can be immutable but you can also extend them or modify them at runtime. When you do so you affect all objects that inherit that prototype.
You have classes, abstract classes, interfaces, override, virtual, sealed, etc You mainly have objects. Depending on your preferences you have classes, constructor functions, etc. But overall it is simpler, it requires less elements and rules.
C# classes promote rigid taxonomies. This requires a lot of additional code and artifice to come to good designs JavaScript prototypical inheritance is more flexible. Composing objects is very straightforward.
C# doesn’t support multiple inheritance. JavaScript lets you compose an object with as many prototypes as you want. (Concatenative inheritance)
C# has great support for information hiding JavaScript achieves information hiding via closures and symbols.

Where C# focuses on classes and creating taxonomies, JavaScript focuses on objects and can achieve a class-free inheritance that is much more flexible and extensible than the C# counterpart. Where C# classes are immutable at runtime, JavaScript prototypes can be augmented or modified at runtime. Where C# provides a lot of keywords and constructs that let you be very thorough and explicit about how someone would interact or work with a class of your creation, JavaScript does away with these concepts in favor of a simpler inheritance model. Where C# provides a pretty clear path on how to do inheritance, JavaScript gives you so much freedom that it can be daunting at times.

Now that we’ve seen the differences between C# and JavaScript inheritance models, let’s dive into prototypical inheritance.

JavaScript Prototypical Inheritance

We can distinguish between two types of prototypical inheritance:

The first one and most common is delegation-based inheritance. In delegation-based inheritance object and prototype establish what is known as a prototypical chain or prototype chain where property or method calls are dispatched or delegated from the object to the prototype.

The second is concatenative inheritance where an object is merged with a prototype and thus gains its properties and methods. The merge of object and prototype consists in copying or concatenating the properties of the prototype into the object.

But what are object prototypes really?

What About ES6 Classes? Don’t They Bring Class-Based Inheritance to JavaScript?

If you have had the opportunity to look at ES6 classes you may be wondering. Well this looks like class-based inheritance, doesn’t it? Well the syntax is a little bit deceptive because although ES6 classes really look like C# classes the reality is very different: ES6 Classes are just syntactic sugar over the existing inheritance model.

We will dive deeper into ES6 classes later in these series and you’ll be able to see the equivalent of a ES6 class in plain class-less JavaScript code.

Object Prototypes

A prototype is, in essence, just an object. How does an object become a prototype? Well it depends if you use an object initializer, Object.create or a constructor function to create your objects. Let’s take a look at all of these approaches.

You can experiment with all examples in this article directly within this jsBin.

Object Prototypes with Object Initializers

If you use object initializers you can define a prototype by setting the __proto__ property of an object to another object that will effectively become a prototype. For instance, if we have the minion from previous examples:

1
2
3
4
5
let minion = {
  hp: 10,
  name: 'minion',
  toString(){ return this.name;}
};

And then we devise a new spell to summon a giantScorpion. We can set minion as a prototype of giantScorpion by using its __proto__ property (available from ES6 1).

1
2
3
4
5
6
7
let giantScorpion = {
  '__proto__': minion, // here we set the minion as prototype
  name: 'scorpion',
  stings() {
    console.log(`${this} pierces your shoulder with its venomous sting`);
  }
}

And TaDa! Now minion is the prototype of giantScorpion, that is, there is a prototypical inheritance relationship between them. If we try to access properties that only exist in minion via giantScorpion we will be able to see the prototypical chain in action:

1
2
3
// access a prototype property via prototype chain
console.log(`giant scorpion has ${giantScorpion.hp} hit points`);
// => giant scorpion has 10 hit points

Indeed we can see how giantScorpion which doesn’t have an hp property itself is accessing the hp property of its prototype. And what happens with the name property that is shared by both?

1
2
3
4
5
// if a property is shared between an object and its prototype
// there's no need to traverse the prototype chain
// the nearest property wins
giantScorpion.stings();
// => scorpion pierces your shoulder with its venomous sting

In this example we call the stings method that in turn calls the toString method which returns this.name. Because the name variable exists in the giantScorpion object, there’s no need to traverse the prototypical chain. As a result the property giantScorpion.name is used to generate the string representation of giantScorpion and we get "scorpion pierces your shoulder..." (instead of "minion pierces your shoulder...").

From here on you have two options in regards to how to use your prototype, you can use one prototype instance per object instance or share a prototype across many objects. While there’s nothing stopping you from one prototype per object, the real benefits of inheritance in terms of code reuse come from sharing the same prototype across several objects:

1
2
3
4
5
6
7
8
9
10
11
12
let smallScorpion = {
  '__proto__': minion, // here we set the minion as prototype
  name: 'small scorpion',
  stings() {
    console.log(`${this} pierces your shoulder with its tiny venomous sting`); }
};
let giantSpider = {
  '__proto__': minion, // here we set the minion as prototype
  name: 'giant spider',
  launchWeb() {
    console.log(`${this} launches a sticky web and immobilizes you`); }
};

Where the properties and methods in minion are contained within a single object. Whereas if we ignored inheritance we would need to define and allocate them in each specific derived object (in this case giantScorpion, smallScorpion and giantSpider) with the additional memory footprint.

You may be wondering, wait, the minion object had a property hp, doesn’t that mean that all derived objects are coupled? That if I change hp in one object it will affect all others?

Well spotted! When you use the same prototype with several objects you want to avoid storing state in your prototype. While having properties with primitive values such as numbers and strings won’t couple your objects, having properties with arrays or objects as values will definitely couple them. This can be better illustrated with an example.

First, if you try to set the value of a property located in your prototype you’ll just create a new property in the derived object that will shadow that of the prototype. Properties with primitive values in prototypes act sort of as initial or default values:

1
2
3
4
5
6
7
8
9
console.log(`Small scorpion has ${smallScorpion.hp} hp`);
// => Small scorpion has 10 hp
smallScorpion.hp = 22;

console.log(`Small scorpion has ${smallScorpion.hp} hp`);
// => Small scorpion has 22 hp

console.log(`Giant Spider *still* has ${giantSpider.hp}`);
// => Giant spider still has 10 hp

If you however try to interact with prototype properties holding objects or arrays, all objects that share that prototype will be affected (since they share the same prototype and the same reference to these properties):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Imagine that a minion had a stomach
// what a wonderful thing stomachs are
minion.stomach = [];

// if a giant scorpion eats an elf
giantScorpion.stomach.push('elf')
// we can verify that yeah, it has eaten an elf
console.log(`giant scorpion stomach: ${giantScorpion.stomach}`);
// => giant scorpion stomach: elf

// but so has the spider
console.log(`giant spider stomach: ${giantSpider.stomach}`);
// => giant spider stomach: elf
// Waaaat!?

So the most common practice when you share a prototype across many objects is to only place methods in the prototype, and keep the state in the object itself.

By the by, did you notice something special in the previous example? I added a stomach property to minion and magically all objects with that prototype got access to that property.

Well a very interesting characteristic of prototypical inheritance is that it allows you to augment all objects that share a prototype at runtime by augmenting the prototype itself. This means that if we add a new method eats to minion:

1
2
3
4
5
6
// A cool thing is that if you augment a prototype
// you automatically augment all its derived objects
minion.eats = function(food){
  console.log(`${this} eats ${food} and gains ${food.hp} health`);
  this.hp += food.hp;
};

All objects that have minion as prototype will automatically gain that method by virtue of the prototype chain:

1
2
3
4
5
6
giantScorpion.eats({name: 'hamburger', hp: 10, toString(){return this.name}});
// => scorpion eats hamburger and gains 10 health 
smallScorpion.eats({name: 'ice cream', hp: 1, toString(){return this.name}});
// => scorpion eats ice cream and gains 11 health 
giantSpider.eats({name: 'goblin', hp: 100, toString(){return this.name}});
// => giant spider eats hamburger and gains 100 health 

Awesome right?

Object Prototypes with Object.Create or OLOO

__proto__ is in an Annex of the ECMA standard and only works in browsers. If you don’t feel comfortable with this, or you are working with JavaScript in another environment like node.js then you can use ES5 Object.create.

Object.create let’s you create a new object that will have as prototype another object of your choice. It works like a factory function for creating objects with a given prototype. For instance, if we adapt this example:

1
2
3
4
5
6
7
let giantScorpion = {
  '__proto__': minion, // here we set the minion as prototype
  name: 'scorpion',
  stings() {
    console.log(`${this} pierces your shoulder with its venomous sting`);
  }
}

To use Object.create it would look like this:

1
2
3
4
5
let newGiantScorpion = Object.create(minion);
newGiantScorpion.name = 'scorpion';
newGiantScorpion.stings = function(){
    console.log(`${this} pierces your shoulder with its venomous sting`);
};

Just like with __proto__ the result of this snippet of code is a new object newGiantScorpion with a couple of properties (name and strings) that has the minion object as prototype.

You can achieve a more compact syntax if you use ES6 Object.assign:

1
2
3
4
5
6
7
let newGiantScorpion = Object.create(minion);
Object.assign(newGiantScorpion, /* new giant scorpion properties */{
    name: 'scorpion',
    stings(){
    console.log(`${this} pierces your shoulder with its venomous sting`);
    }
};

Object.assign copies the properties from one (or several) objects into a target object of your choice (in this case newGiantScorpion).

Defining Prototypes with Constructor Functions

Defining objects as prototypes with constructor functions works in a slightly different way than with object initializers and Object.create. Imagine that we have a TeleportingMinion that can teletransport itself wherever it desires:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//let minion = {
//  hp: 10,
//  name: 'minion',
//  toString(){ return this.name;}
//};

// prototype inheritance works slightly different with constructor functions
function TeleportingMinion(){
    let position = {x: 0, y: 0};
    this.teleportsTo = function(x, y){
  console.log(`${this} teleports from (${position.x}, ${position.y}) to (${x}, ${y})`);
      position.x = x;
      position.y = y;
    };
    this.healthReport = function(){
  console.log(`${this} has ${this.hp} health. It looks healthy.`);
    };
}

In this case where we have a constructor function, instead of using the __proto__ property we assign the prototype to the prototype property of the constructor function, that is:

1
2
TeleportingMinion.prototype = minion;
TeleportingMinion.prototype.constructor = TeleportingMinion;

From his point forward every object that we create with this constructor function will have the minion object as prototype:

1
2
3
4
5
6
7
let oneCrazyTeleportingMinion = new TeleportingMinion();
oneCrazyTeleportingMinion.healthReport();
// => minion has 10 health. It looks healthy.

let anotherCrazyTeleportingMinion = new TeleportingMinion();
anotherCrazyTeleportingMinion.healthReport();
// => minion has 10 health. It looks healthy.

Creating Longer Prototype Chains

You are not limited to a one level deep prototype chain with just an object and a prototype, you can create big inheritance structures just like in C#.

Let’s say that we want to create a wizard. We can make it inherit from the TeleportingMinion:

1
2
3
4
5
6
7
let wizard = {
   '__proto__': new TeleportingMinion(),
   name: 'Evil wizard',
   castsFireballSpell(target){
     console.log(`${this} casts fireball spell and obliterates ${target}`);
   }
};

Effectively establishing a prototype chain that looks like wizard => teleportingMinion => minion where our new object wizard now inherits the behavior of a teleportingMinion and a minion:

1
2
3
4
5
6
7
8
9
10
11
12
// the wizard can cast fireballs
wizard.castsFireballSpell('sandwich');
// => Evil wizard casts fireball spell and obliterates sandwich
// damn that was my last sandwich

// it can teleport
wizard.teleportsTo(1,2);
// => Evil wizard teleports from (0, 0) to (1, 2)

// and it has hit points
wizard.healthReport();
// => Evil wizard has 10 health. It looks healthy.

And we can do the same with constructor functions, this time with a Druid:

1
2
3
4
5
6
7
// you can do the same with a constructor function
function Druid(){
  this.name = 'Druid of the Forest';
  this.changesSkinIntoA = function(skin){
    console.log(`${this} changes his skin into a ${skin}`);
  }
}

This time instead of using the __proto__ property we use Druid.prototype and create a prototype chain like Druid => teleportingMinion => minion:

1
2
Druid.prototype = new TeleportingMinion();
Druid.prototype.constructor = Druid;

And now any druid object that we instantiate using the new operator on the Druid constructor function will inherit all its mighty abilities from teleportingMinion and minion:

1
2
3
4
5
6
7
8
9
10
11
12
13
let druid = new Druid();

// the druid can change skin
druid.changesSkinIntoA('wolf');
// => Druid of the Forest changes his skin into a wolf

// it can teleport
druid.teleportsTo(2,2);
// => Druid of the Forest teleports from (0, 0) to (2, 2)

// and has hit points
druid.healthReport();
// => Druid of the Forest has 10 health. It looks healthy.

What About Concatenative Protypical Inheritance?

In this first dive into OOP we are going to go from prototypical inheritance, to mimicking classical inheritance and to ES6 classes, the most natural path for a C# developer coming to JavaScript. The flavor of prototypical inheritance that enables having a similar inheritance flow to that of classes is the delegation-based inheritance and that’s why we have focused on it first.

We will come back to concatenative inheritance and object composition after we’ve reviewed ES6 classes. Stay tuned!

Object Initializers vs Object.create vs Constructor Functions

Object Initializers __proto__ Object.create OLOO Constructor Functions
Very simple and readable way to setup prototypical inheritance. Simple way to setup prototypical inheritance. Less terse than initializers. You can use `Object.assign` to make it more terse. Less straightforward as you set the prototype of the *constructor function* and not an object.
Need to set it every time you create an object unless you use a factory function. Need to set it every time you create an object unless you use a factory function. Set it once on the *constructor function* and it is reused for all instances created afterwards.
It is only reliable in ES6 and on web browers. Available from ES5 (on all modern browsers and other JS environments).j Supported in any environment.
Simple syntax for defining getters and setters. Can only define getters and setters via `Object.defineProperty` Can only define getters and setters via `Object.defineProperty`

Concluding

Let’s summarize what you’ve learned so far about prototypical inheritance. JavaScript doesn’t have the concept of traditional class-based inheritance since it doesn’t have real classes. Instead JavaScript inheritance revolves around objects, just simple objects. You can use any object as a prototype and create new objects that are based on this prototype and that can inherit properties and methods from it.

In order to establish an inheritance relationship between an object and a prototype you can either use the __proto__ property within an object initializer, Object.create or the prototype property within a constructor function. The two first methods that completely prescind of functions are also called OLOO (Objects Linked to Other Objects) and provide a simpler approach to prototypical inheritance as there’s one less element you need to think about (the constructor function). You can have an inheritance tree with many levels of depth where an object has a prototype which in turn has a prototype, and so on.

Whenever you try to access a property or method of an object that has a prototype the JavaScript runtime will try to find that property or method within the object itself, if it can’t find it, it will continue down the prototypical chain until it finds it. This is known as delegation-based inheritance and is the most common flavor of prototypical inheritance in JavaScript. If you use the same prototype object for several objects in this delegating fashion you should avoid storing state in the prototype since it may couple all your “derived” objects.

There’s also concatenative inheritance which consists on copying properties from a prototype to an object and which leads to object composition. We will take a look into that later within the series.

Have a great week ahead!

Interested In Learning More JavaScript? Buy the Book!

Are you a C# Developer interested in learning JavaScript? Then take a look at the JavaScript-mancy book, a complete compendium of JavaScript for C# developers.

a JavaScriptmancy sample cover

More Articles in These Series


  1. The __proto__ property has been available in some browsers prior to ES6 but it wasn’t part of the ECMA standard up until ES6. Because it not being part of any standard and thus not having a specific defined behavior it was very unreliable to use. With ES6 you can use it with your object initializers as it makes very easy to understand the prototype chain. __proto__ only works on browsers, if you are working with node you can use Object.create and follow a very similar flow.

Comments