Introduction

Classes in JavaScript are just a different way of representing constructor functions and methods from “prototype objects”, but without fundamentally changing the inheritance mechanism itself. Background class inheritance is the well-known mechanism of “method delegation” through the “prototype chain of inheritance”.
In JavaScript, the reserved word “class” only indicates that the query is a special type of function, which, unlike a regular function, cannot be called (invoke), but can only be used with the reserved word “new”.
This functionality was introduced into the language with the ES2015 standard, with the intention of improving readability and facilitating the writing of object-oriented programs. When defining the appearance of the syntax, the designers of the standard were guided by the idea of creating such a syntax that will resemble the syntax of most “class” languages”, and thus help programmers who already program in a “class” language” to more easily adopt the JavaScript syntax.
If in the following examples we compare parts of the code written with different syntaxes, we can see that the class syntax is better readable compared to the ES5 syntax:
Example: ES5 syntax
|
1 2 3 4 5 6 7 8 9 |
var Osoba = (function () { function Osoba(ime) { this.ime = ime; } Osoba.prototype.objavi = function () { console.log("This is it" + this.ime); }; return Osoba; })(); |
Example: Class Syntax
The layout of the class is very similar to the object creation syntax according to the ES5 standard, although it is more readable due to fewer lines and unnecessary parts removed:
|
1 2 3 4 5 6 7 8 |
class Osoba{ constructor(ime) { this.ime = ime; } objavi() { console.log("This is it" + this.ime); } } |
NOTE:
Unlike other “class languages”, creating objects in Javascript (even when using classes) is not static ie. is not immutable after the object‘s declaration. In JavaScript, an object after instantiation remains “bound” to the class, with its “prototype object” through the chain of inheritance. Therefore, if we subsequently add or change a method in the class itself, that change will be felt by every object instantiated on the basis of that class.
Basic features
Creating classes
Since classes are still just functions, as with all functions there are two ways to create them:
- Function declarations
This is the most common way to write classes:
123class nazivKlase {// CLASS BODY}The main difference between a function declaration and a class declaration is that classes are not “hoisted” when compiled to the beginning of the program code as a function. So care should be taken to define the class before using “new” to create a new object.
- Function expressions
This approach is less commonly used, but a completely legal way to create classes:
123var nazivKlase = class {// CLASS BODY}As with ordinary “function expressions“, there is no hoisting here either.
NOTE:
The body of the class is executed in “strict mode”.
Methods
Ordinary methods
We define methods as functions, but without the function keyword. Although it should be noted that the methods inside the class have the “enumerable” property set to false, which means that no “for..in” loops are available at the start.
Constructor method
A constructor is a special method that creates and initializes an object. This method is the counterpart to the constructor function from ES5, it defines everything needed to initialize a new object, with the note that there can only be one constructor method within a class.
|
1 2 3 |
constructor { // METHOD BODY } |
Static methods
Static methods are methods that do not belong to the instantiated object, but to the class itself, therefore they cannot be accessed from the instantiated object. The only access to these methods is through the class itself. Such methods are defined by using the reserved word “static” in front of the method name.
|
1 2 3 |
static nazivMetode { // METHOD BODY } |
Get and Set methods
In many programming languages, access to private properties is achieved using special methods called “get-era” and “set-era”. This is also the case with JavaScript, where such methods within a class are indicated by adding the reserved word “get/set” before the method name itself.
Example
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
class Heroj { constructor() { this._health = 100; } get objaviStanje() { console.log("Health je " + this._health); } set primljenUdarac (udarac){ if (this._health - udarac > 0){ this._health -= udarac; this.objaviStanje; } else { console.log("Heroj je mrtav!");// } } set ozdravljenje (pomoc){ if (this._health + pomoc >100){ this._health = 100; this.objaviStanje; } else { this._health += pomoc; this.objaviStanje; } } } var batman = new Heroj(); batman.objaviStanje // Returns: "Health je 100" batman.primljenUdarac=50; // Returns: "Health je 50" batman.ozdravljenje=20; // Returns: "Health je 70" batman.primljenUdarac=80; // Returns: "Heroj je mrtav!" |
NOTE:
Get/Set methods are defined within the class as methods, but they are accessed in the same way as if they were properties of the object.
a) This method is accessed inside some other method as if it were an ordinary object property:
|
1 |
this.objaviStanje |
b) The “Get” method is also called in the newly generated object as a property of the object:
|
1 |
batman.objaviStanje |
c) Even the “set” method is approached as assigning a value to an ordinary object property:
|
1 |
batman.ozdravljenje=20; |
Class inheritance
Syntax
Inheriting classes requires the extends keyword and the name of the inherited object to be used in the class declaration itself. The declaration in the following example Successor extends Parent “tells” us that the Successor.prototype object is connected to the Parent.prototype object.
|
1 2 3 |
class Naslednik extends Roditelj { // CLASS BODY } |
When creating a new class that inherits another class, we have the obligation that when defining the constructor function, we must call the constructor function of the parent class inside it. Reservations The word super indicates the parent constructor function, and the “super()” method is used to call the constructor of the base class. This base class constructor call must be made at the very beginning of the constructor function! (because it defines the keyword “this”. )
If we do not call the “super()” method within our constructor function, the JavaScript engine will throw an error: “Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor”. The error will occur even if we don’t use “this” in our constructor, as in the following example:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
class Osoba { constructor() { console.log("Ja sam osoba.") } } class Programer extends Osoba { constructor() { console.log("Ja sam Programer!") } } var dragoljub = new Programer(); // ReturnsError: "Must call super constructor in derived class before accessing 'this' or returning from derived constructor" |
So, if we have a constructor method in the new class (even if it is empty) “we have to call the super() method”!
EXPLANATION:
By calling the super() method, we execute the entire code from the basic constructor function and define that this points to the newly generated objects.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Osoba { constructor() { console.log("Ja sam osoba.") } } class Programer extends Osoba { constructor() { super(); console.log("Ja sam Programer!") } } var dragoljub = new Programer(); // Returns: "Ja sam osoba." // Returns: "Ja sam Programer!" |
What happens if the new class does not have a constructor() method defined?
|
1 2 3 4 5 6 7 8 9 10 |
class Osoba { constructor() { console.log("Ja sam osoba!") } } class Programer extends Osoba { } var dragoljub = new Programer(); // "Ja sam osoba!" |
In this case, JavaScript creates its own internal (hidden) constructor function for us, which looks like this:
|
1 2 3 |
constructor (...args){ super(...args); } |
Base class property access
Property frombase classes are accessed by default with “this.property”:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Osoba { constructor(ime) { this._ime = ime; } } class Programer extends Osoba { constructor(ime) { super(ime); console.log(this._ime + " je programer!") } } var dragoljub = new Programer("Dragoljub"); // Returns"Dragoljub je programer!" |
Base class method access
The reserved word super represents the “parent” constructor, so when we need access to a method from the base class, we access it with “super.methodname”:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class Osoba { constructor(ime) { this._ime = ime; console.log("My name is defined in the base class."); } objaviIme() { console.log("Ja sam " + this._ime); } } class Programer extends Osoba { constructor(ime, programskiJezik) { super(ime); this.programskiJezik = programskiJezik; } objaviJezik() { super.objaviIme(); console.log(this._ime + 'uses' + this.programskiJezik + '.'); } } var dragoljub = new Programer('Dragoljub', 'JavaScript'); dragoljub.objaviJezik(); // "My name is defined in the base class." // Returns: "Ja sam Dragoljub" // Returns: 'Dragoljub uses JavaScript.' |

