ES6 (a JavaScript version released in 2015) introduced ‘classes’ to JavaScript. This change did not add class-based inheritance model to the language but was more like an eye candy providing a new syntax for implementing prototypical inheritance, a new way to write the same behavior. So how does it work?

The class in JavaScript is a special kind of a function. It has a prototype property and can be used in a constructor call (with new operator).

Let’s see one in action.

Disclaimer: some knowledge of prototypes and object relationships in JavaScript is required.

Example

class Person {
  constructor(name) {
    this.name = name;  
  }
  
  logName() {
    console.log(this.name);
  }

  static logDate() {
    console.log(new Date(Date.now()).toLocaleString('lt'));
  }
}

const timmy = new Person('Timmy');

timmy.logName(); // Timmy
Person.logDate(); // logs current date

Let’s take this step by step.

We see a constructor method inside the class body. It is a special method used to initialize an object created by making constructor call on the class. In simple terms the code in it’s body runs when calling a class with new.

You can pass parameters to it that would be used on that call.

Here we pass ‘Timmy’ as an argument when we make a constructor call. That value is used in a constructor method as a name parameter.

Next we see a logName() method. Regular methods like this end up on the object on class’s prototype property. So Person.prototype gets the logName() method.

Finally we see a static method logDate(). Static methods actually end up as properties on the Person (class) object itself. We don’t need to create a new object to call it as we see in the example. It’s worth noting that static methods don’t get prototype property and cannot be used with new keyword.

Result:

  • Class’s constructor method called when calling Person class with new operator
  • regular methods end up on Person.prototype – class’s prototype property
  • static methods end up on Person – the class itself

Let’s compare previous example to the usual function usable in construction calls.

Example

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

Person.logDate = function() {
  console.log(new Date(Date.now()).toLocaleString('lt'));
}

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

const timmy = new Person('Timmy');

timmy.logName(); // Timmy
Person.logDate(); // logs current date

This would be our equivalent in the old fashioned way.

  • The class constructor method body goes to the Person function’s body.
  • logDate()* method is added to Person function as an object property.
  • logName() function is added to Person.prototype manually.

* The logDate() method in this example does have a prototype property unlike the static method found in class and can be called with new operator.

Super extendable classes

Take a look at this.

Example

class Animal {
  constructor(name) {
    this.name = name;  
  }
  
  logName() {
    console.log(this.name);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }

  bark() {
    console.log('woof');
  }
}

const butkus = new Dog('Butkus');

butkus.bark(); // woof
butkus.logName(); // Butkus

Here we declare two classes Animal and Dog. We want the Dog to inherit functionality from Animal, to have sorta parent-child relationship mimicking class-based inheritance. To do that we use extends keyword.

class Dog extends Animal, what does that do?

It means that Dog.prototype (which is [[Prototype]] of the newly created object butkus) gets it’s [[Prototype]] set to Animal.prototype. What we get is a nicely readable (and writtable) prototype chain.

  • butkus’s [[Prototype]] is Dog.prototype (which is basically the content of Dog class body)
  • Dog.prototype’s [[Prototype]] is Animal.prototype

We can’t find a certain property on butkus? Let’s look for it in Dog class. Not there? Let’s look in Animal class.

This is the so called syntactical sugar arguably making these relationships easier to understand by just looking at the code.

One more thing that needs explaining is the super() method found in Dog class’s constructor method. What it does is it call’s the constructor method of the extended class passing arguments to it.

In our example we have the Dog class with a constructor method that calls super(). We use super() to call the constructor method of it’s super class (constructor method of the Animal class in this case). That is why passing an argument to new Dog(‘Butkus’) sets the name property of the object (it uses Animal constructor method).

  1. we call new Dog(‘Butkus’);
  2. Dog class’s constructor method is called using ‘Butkus’ as a value for name parameter, it calls super(name);
  3. Animal class’s constructor method is called taking it’s parameter value from the super call.

Now let’s look at the example written without JavaScript class features

Example

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

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

function Dog(name) {
  Animal.call(this, name);
}
Dog.prototype.bark = function() {
  console.log('woof');
}

Object.setPrototypeOf(Dog.prototype, Animal.prototype)

const butkus = new Dog('Butkus');

butkus.bark(); // woof
butkus.logName(); // Butkus

Still working but it’s probably way harder to understand the relationships this way.
Though even if it might be more difficult to understand the code now, it is actually closer to reality as there are way less hidden behavior and we are using prototypes anyway even though we are writing classes in the first example.

Other differences between class and function declarations

Class declarations are not hoisted

Example

console.log(Person); // ReferenceError

class Person {};

Class content inside the curly braces is always in strict mode.

Example

class Person {
  static tryIt() {
    it = 1;
  }
}

Person.tryIt(); // ReferenceError

In a regular function without strict mode turned on it would be set as a global object’s property.

Summary

We see JavaScript classes being used in modern programming often, many popular tools (i.e. React) embrace it, and it is necessary to understand what it is and how it works. Hopefully this article helped you deepen your knowledge of JavaScript classes and inheritance and was a fun read.

Best Wishes, Vladas Končius


Leave a Reply

Your email address will not be published.