Javascript Classes and Inheritance ES6, ES5

Jorge Moller
10 min readMar 4, 2019

Every developer should strive to write code as reusable as possible. Over time it has huge advantages; writing less code, making it more readable, maintainable and extensible are just a few that comes to mind.

In the following post, we are going to focus on inheritance, and how we can harness its power to write reusable code. Bear in mind that this is one of many methods to reuse code, but I have found it to be a critical aspect when writing javascript applications.

Classes in ES6

Classes are special functions, and just as you can define a function expression and a function declaration the class syntax has two components:

  1. Class declarations
    class Reactangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
  1. Class Expressions
    // unnamed
var Rectangle = class {
constructor(width, height) {
this.width = width;
this.height = height;
}
}

//named
var Rectangle = class Rectangle{
constructor(width, height) {
this.width = width;
this.height = height;
}
}

Important: an important difference between function declarations and class declarations is that function declarations are hoisted and class declarations are not.

Example:

var r = new Rectangle(); // Reference error

class Rectangle {
...
}

Constructors in ES6

The constructor function is a special method for creating and initializing an object created with class.

Something to take into consideration is that there can be only one constructor within a class, otherwise, a SyntaxError will be thrown.

A constructor can also make use of the super keyword to call the constructor of the superclass.

Inheritance in ES6

The extends keyword is used in class declarations to create a class as a subclass or child of another class.

For example:

class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + 'makes noise');
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
super.speak();
console.log(this.name + 'barks');
}
}
let dog = new Dog('Bolt', 'White German Shepherd')
dog.speak();
// Bolt makes noise
// Bolt barks

This is the very (very) basics of classes and inheritance in ES6. But, back in the day when javascript did not have the class, extends and super keyword, how did all of this work?

I think it’s imperative to understand how things worked before ES6, because when we do, we know what is happening under the hood with classes and inheritance in ES6.

So let’s dive into the classical way of defining classes and implementing inheritance in JavaScript.

Classical Pattern #1 — The default Pattern

To understand inheritance, we first need to understand how the prototype chain works.

The default method to create inheritance is to create an object using a constructor and then assign this object to the child’s prototype. Here is how it look like

function Parent(name) {
this.name = name || 'Adam';
}

// Adding some functionality to the person's prototype
Parent.prototype.say = function() {
return this.name;
}

// Empty child constructor
function Child(name) {};

function inherit(Child, Parent) {
// We are just pointing the prototype property of Child to // an object, in this case the object is an instance of the Parent.
Child.prototype = new Parent();
}

inherit(Child, Parent);

// Here the kid gets functionality from the Parent() instance //via the prototype.
var kid = new Child();
kid.say(); // 'Adam'

Using this pattern we inherit both own properties and prototype properties and methods.

Now, let’s dive under the hood and see what happens with the prototype chain that makes this behavior possible.

In the following picture, we have two code blocks, the one on the left is when we create an object using new Parent(). In this case, we create a code block that holds data for the name property. The one on the right is the prototype object of Parent.

Behind the scenes, the left code block has a proto link that points to the prototype object of the constructor function Parent().

Now, let's see what happened when a new object is created, using var kid = new Child() (After using the inherit function implemented by us).

Remember that the hidden proto link always points to the prototype property of that object. In our case, in the inherit() function, we said that we want our Child.prototype pointing to the new Parent() object. This is why the hidden proto link now points to the new Parent() object.

What happens when we do kid.say(). First it looks up that method in the Child object, and because it doesn’t exist there, it goes up through the prototype chain and looks it up in the Parent object, which doesn’t have it either, so it searches one more time in the proto link (going up in the prototype chain) and finds it there. Now, the say() method has a reference to this.name which needs to be resolved. The lookup starts again, in this case, this points to new Child() object which doesn’t have name. Then, the code block in new Parent() is consulted and it does have a name property with the value “Adam”.

Let’s take a look at one more example:

var kid = new Child();
kid.name = "John";
kid.say(); // John;

When we set kid.name, we are not overriding the name property of the parent, we are creating a new property owned by the Child object. When we do kid.say(), the same thing happens when trying to look up the say() method. But this time, looking up this.name is quick, because the property is found immediately in the Child’s object.

Classical Pattern #2 — Rent a constructor

In this pattern, we make use of the parent constructor, passing the child object to be bound to this and also forwarding any arguments:

function Child(a, b, c, d) {
Parent.apply(this, arguments);
}

Bear in mind, that by using this pattern we can only inherit properties added to this inside the parent constructor, we do not inherit members that were added to the prototype.

By using this pattern, we basically get a copy of the inherited members, unlike the pattern #1 where we only get references.

Let’s look at an example:

function Animal() {
this.types = ['reptile', 'insect'];
}
var animal = new Animal();

// A Lion that inherits from animal using the pattern #1
function Lion() {}
Lion.prototype = animal;
var lion = new Lion();

// A Monkey that intherits from animal using the pattern #2
function Monkey() {
Animal.call(this);
}
var monkey = new Monkey();

console.log(animal.hasOwnProperty('types')); // true
console.log(lion.hasOwnProperty('types')); // false
console.log(monkey.hasOwnProperty('types')); // true

lion.types.push('felidae');
monkey.types.push('mammals')
console.log(animal.types.join(', ')); // "reptile, insect, felidae"
console.log(monkey.types.join(', ')); // "reptile, insect, mammals"

In this example, we can clearly see that the lion object modifies the types property of the animal object, unlike monkey which only modifies its own copy of types.

Let’s see how the prototype chain looks like when using this pattern.

    function Parent(name) {
this.name = name || 'Adam';
}

// Add some functionality to the prototype
Parent.prototype.say = function() {
return this.name;
}

// child constructor
funciton Child(name) {
Parent.apply(this, arguments);
}

var kid = new Child('John');
kid.name; // 'John'
typeof kid.say; // undefined

The main downside of using this pattern is that we don’t inherit anything from the prototype, and the prototype is the place to add reusable methods and properties.

An advantage is that you get copies of the parent’s own members ( all members of this instance, will get copied)

Let’s look at yet another example of how to approach classes and inheritance in javascript that will address the issues from pattern #2.

Classical Pattern #3 — Rent and Set Prototype

In this pattern we will combine the previous two, we are first going to borrow the constructor and then also set the child’s prototype to post to a new instance of the constructor:

    function Child(a, b, c, d) {
Parent.apply(this, arguments);
}
Child.prototype = new Parent();

By following this pattern, we get objects that hold copies of the parent’s own members and also we have references to the parent’s reusable functionality (because of the prototype).

A downside is that we are calling the constructor twice, so it not that efficient.

Let’s see this pattern in action with the following example:

function Parent(name) {
this.name = name || 'Adam';
}

Parent.prototype.say = function() {
return this.name;
}

function Child() {
Parent.apply(this, arguments);
}
Child.prototype = new Parent();

var kid = new Child('John');
kid.name; // 'John'
kid.say(); // 'John'
delete kid.name;
kid.say(); // 'Adam'

Unlike the previous pattern, say() is now inherited. There is something interesting about this example, when we delete the name property from kid and then call say(), javascript looks up the name property of child and doesn’t find it there so it goes up the prototype chain and looks it up in the parent object. Parent has a property name set to ‘Adam’, so that what gets printed in the console.

Classical Pattern #4 — Share the Prototype

In the next pattern, we are going to see how to get rid of the call to the parent constructor.

We know that reusable members should go to the prototype and not this. Therefore, anything that is worth inheriting should be in the prototype. What would happen if we set the child’s prototype to be the same as the parent’s prototype? Let’s see.

    function inherit(C, P){
C.prototype = P.prototype;
}

This is how the prototype chain would look like:

Let’s look at an example:

function Parent(name) {
this.name = name || 'Adam';
}

Parent.prototype.say = function(){
return this.name;
}

function Child(name) {
// How can I inherit the name property from Parent!!??
this.name = name;
}

function inherit(C, P){
C.prototype = P.prototype;
}

inherit(Child, Parent);

var kid = new Child('John');
console.log(kid.name); // John
console.log(kid.say()); // John

Well, although both child and parent objects share the same prototype, therefore have access to the say() function, the children objects don’t inherit the name property. A workaround to this would be to add the Parent.call(this, name); in the Childs constructor, to get a copy of the name property.

ES6 Classes under the hood

Let’s wrap up this post going under the hood of ES6 classes, what kind of magic are they doing??!!.

Let’s just create a very simple Animal class, and see to what it compiles to in ES5.

Right off the bat, we can see that an immediate function gets executed and assigned to the Animal variable.

The second thing to notice is that a constructor gets created, and a strange function (_classCallCheck) gets called with the instance of the constructor and the constructor (function) itself. Let’s see what this function does.

function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}

It just checks if we are calling the constructor as a function or with the new keyword. Remember that the new keyword tells javascript to create an empty object and assign that empty object to this inside the constructor.

var animal = new Animal('lion'); // Everything ok

var animal = Animal('lion');
// Not ok, we are calling the constructor function without the new //keyword, therefore javascript uses the immediate this from it's //lexical scope, which in our case points to the window object, and //the window object is not an instance ot the Animal constructor.

Great, so far so good, now in line 6 it sets the name member of this to the value we pass as an argument to the constructor, nothing weird here.

In line 9 is where the magic happens. There is a method _createClass that gets called. This method passes two parameters, the Animal function, and an array of objects containing a key property that hold the name of the functions declared in our Animal class, and a value property that holds, well, the function itself.

Let’s peek this _createClass function, shall we?

In line 11 we notice that it returns a function that takes a Constructor (in our case the Animal function) and also some protoProps (in our case, the array with object that holds the key and value for our class functions). For now, let’s not worry about the staticProps.

It checks if there are protoProperties (functions), if so, it calls the defineProperties function with the Constructor.prototype (Animal.prototype in our case) and with the protoProperties (array of object that hold the functions).

Let’s analyze the signature of the defineProperties function. It takes a target, which is the constructor.prototype and some props which are an array of objects that holds our class functions.

Now let’s dive into the function and explain what it does in each and every step.

After all of this magic happens, it just returns the constructor.

Conclusion

I really hope you have found something useful in this post, if you have any comments or suggestions don’t hesitate to write them in the comment section.

--

--

Jorge Moller

JavaScript developer trying to understand how things work…☕️👨🏼‍💻 https://jorgemoller.dev