ES6: Classes

20min read

Object-oriented programming languages like PHP, Java or C++ have natively classes built-in to their language. Don’t get fooled by the new ES6 class syntax. Javascript doesn’t have this feature but rather makes use of its powerful prototypal inheritance concept to “simulate” them or in nicer words “sugarcoat” it. In Javascript you can program both object-oriented and functional because prototyal inheritance is extremly powerful.

This article will start with a quick recap on prototypal inheritance in Javascript, feel free to jump directly to the new class concept.

Prototypal inheritance

Prototypal inheritance and object orientation are complex topics and could easily each on their own fill multiple long articles. This is just a quick recap of the concepts behind.

Lets start with a person object john that has a greeting method that ouputs its name. Remember: if we have a method on an object the this keyword will always point to the object itself.

Lets say now we need to make another person object Jane. How shall we do it? Just copy the previous one and give it different values? What if we need 10 or 100 different person objects? Copy and paste them all with different values?

Allthough it would technically work it really shouldn’t be how a developer approach this task. There is a fundamental concept that is universal to programming which is called Dont Repeat Yourself (DRY). So lets stick to it and make our code dynamic by creating a function that serves us as a blue-print for unlimited person object instances.

Object Constructor Function

First we need to create an object constructor function with the properties we want our person objects to have. Object constructor functions are normal functions which are invoked with the new keyword preceeding it. We define its properties with this.propertyname = propertyname.

In Javascript every function is an object but not every object is a function. Higher-order functions are functions that accept other functions as arguments which is only possible if functions are treated like objects. In order to make an instance of an object we call the object constructor function we want to inherit from with the new keyword beforehand. This way the Javascript engine knows that we intend to create a new instance of that object.

Adding methods to all instances

Now its time to add our greet method to the object constructor function. We want to add it to the object constructor function (our blue-print object) that all instances have access to. We can do this in two ways:

Adding the method from the very beginning directly to our object constructor function (same as we add properties)…

… or add it to the prototype of the object constructor function. Each function has a prototype property object with a constructor method that points to the function itself. Whenever we create a new instance (keyword new) we automatically invoke this constructor method which lives on the prototype of the object constructor function.

Every plain object has a __proto__ object and the __proto__ object of objects created as instances of another object (like in our example) point to the prototype of the object it is created from (remember: All functions are objects but not all objects are functions).

What is important here: Instances of objects (like john) have a reference to the objects they inherit from (like es5_Person). John’s __proto__ points to the prototype of es5_Person. One could also say that john’s __proto__ gets replaced with the prototype of its parent and therefor inherits all its methods. Both objects point to the same spot in memory. Their destinies are linked from now on. If you change the parents prototype you will also change the __proto__ of all their instances.

This also means if we add a method to the prototype of the object constructor function (es5_person) all their instances (john) will have access to it. Even if they are already created.

This works because when we call the method greet on john the Javascript engine looks if it can finds it on the john object first. If it is not present there (like in our example) it goes down the “prototype chain”. Since johns __proto__ points to the es5_Person prototype the Javascript Engine will find the method there and can run it.

Classes

Now that we understood how prototypal inheritance work lets look at ES6 classes but remember: under the hood it is still prototypal inheritance. It starts with the class keyword and the name of our class. It is written without parenthesis, has a constructor function and any method will be written just with its methodname, followed by parenthesis without comma seperation inbetween them.

Subclasses

A subclass extends the functionality of its parent class. All instances of a subclass own the full functionality of the base class but additionally have methods which only instances of this subclass have access to. Let’s define a soldier subclass. Every soldier is a also a person but not every person is a soldier. Let’s look at it first how it was done in ES5 and how it is done now in ES6. Jump directly to ES6 Subclasses if you don’t need this recap.

ES5

In ES5 we would create a new object constructor function with all the properties we want to inherit from the base class (firstname, lastname) and additonally all the properties that only exist in the subclass (weapon). Then we would call the base class within the context of the soldier subclass via call or apply and pass the current this context as well as all the properties of the base class (firstname, lastname). Afterwards we set the property only relevant for the subclass (weapon) normally how we are used to it in the object constructor function.

Now we have a subclass who’s instances have the properties firstname, lastname and weapon but we dont inherit the methods from es5_Person. We don’t have access to its prototype. One solution is to use Object.create() to create a new object on the es5_Soldier.prototype and pass it the es5_Person.prototype. The Object.create() method creates a new object, using an existing object as the prototype of the newly created object. This means on the es5_Soldier.prototype a new object is created which has the es5_person.prototype as its __proto__. This way all instances of es5_Soldier are able to access the methods of es5_Person through the prototype chain.

Any method which we add now to the prototype of our subclass will live there while the methods of its base class can be accessed through the __proto__ of the prototype of the subclass. Since the Javascript Engine goes down the prototype-chain automatically when it doesn’t find it we can call our methods as we are used to on the object. Let’s add a method for all our soldier instances.

Lets now create a new es5_Soldier instance “jackTheSoldier” and try if we can access both the shoot() method of its parent class and the greet() method of the base class es5_Person.

There is also another way to access the greet method from es5_Person but with Object.create(). We can define greet() as a new method on the es5_Soldier.prototype and call from within the function the es5_Person.prototype.greet() method and apply the current this context.

Both is possible but I personally prefer the Object.create() method as it feels more straightforward to me.

Last but not least we can also prove that john can greet() but not shoot() because it has as an instance of es5_Person only access to greet(). Only instances of es5_Soldier can both greet() and shoot().

ES6

In the ES6 class syntax the subclass concept is hugely simplified which is why a lot of developer like it. But remember it is all still prototypal inheritance under the hood. If we want to make es6_Soldier a subclass of es6_person we just use the keyword extends.

Since our es6_Soldier subclass adds a new property (weapon) we have to manually call the constructor function, pass it the base class properties firstname + lastname and then add weapon as a new parameter. The super() method is built-in and is used to call the parents constructor in our current context. If we wouldn’t add any new parameters we could leave it out because…

…would be called otherwise automatically in the class.

If we want to override a parent method we can simply define it with the same name in our subclass and because within the prototype chain it will be before the parent method it will get run. If we however want to build on top of it, we also define it first in our subclass but also call the parent’s method from within via super.methodName(). Overwriting the greet() method from the es6_Person class within the es6_Soldier subclass would look like this:

This is it about classes. If you have any feedback or questions please let me know in the comments.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *