JavaScript classes we’re introduced with ECMAScript 2015, they’re often described as syntactical sugar over JavaScript’s existing structure of prototypical inheritance. So while classes do not introduce a new inheritance model to JavaScript — they do provide syntactical simplicity. This simplicity can help us produce less error-prone & more readable code.

Classes are like functions!

Classes are very similar to functions. Much like functions which have both function expressions and function declarations, classes have two components: class expressions and class declarations.

Class declarations

Lets take a look at how we can define a class using a class declaration. We use the class keyword followed the name of the class:

class Image {
constructor(height, width) {
this.height = height;
this.width = width;
}
}

Hoisting

One important difference between function declarations and class declarations is that function declarations are hoisted and class declarations are not. You first need to declare your class and then access it, otherwise code like the following will throw a ReferenceError:

const p = new Image(); // ReferenceErrorclass Image{}

Class expressions

class expression is the other way to define a class. Class expressions can be named or unnamed. Note that the name given to a named class expression is local to the class’s body. (So it’s retrievable through the class’s name property):

// An unnamed class expression
let Image = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Image.name);
// output: "Image"// A named class expression
let MyImage = class Image {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(MyImage.name);
// output: "Image"

Note: Class expressions are subject to the same hoisting restrictions as described previously in the Class declarations section.

Constructors

The constructor method is a special method within JavaScript that we use to create and initialize an object created with a class. We can only use one method with the name “constructor” within a class.

Our constructor can use the super keyword to call the constructor of the super class (more on this later in the article!).

Instance properties

Instance properties must be defined inside of our class methods:

class Image {
constructor(height, width) {
this.height = height;
this.width = width;
}
}

If we wish to use static class-side properties and prototype data properties, these must be defined outside of the classes body declaration:

Image.staticWidth = 50;
Image.prototype.prototypeWidth = 55;

Field declarations

Whilst the syntax is still considered experimental (it’s not yet adopted by many browsers), public and private field declarations are also worth knowing about — as often you’ll be developing with a Babel which will transpile the syntax for you.

Public field declarations

Let’s revisit our example with the JavaScript field declaration syntax:

class Image {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}

The difference is our fields have been declared up-front. So our class definitions become more self-documenting, and the fields are always present.

Note: the fields can be declared with or without a default value!

Private field declarations

When we use private fields, the definition can be refined like so:

class Image {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}

Private fields (declared with a #) cannot be referenced outside of the class, only within the class body. This ensure that your classes’ users can’t depend on internals, which may change with version changes.

Note: Private fields cannot be created later through assignment. They can only be declared up-front in a field declaration.

Child classes using ‘extends'

We can use the extends keyword with either class declarations or class expressions to create a class as a child of another class.

class Vehicle{ 
constructor(name) {
this.name = name;
}

sound() {
console.log(`${this.name} makes a sound.`);
}
}class Car extends Vehicle{
constructor(name) {
super(name); // call the super class constructor and pass in the name parameter
} sound() {
console.log(`The ${this.name} tooted its horn!`);
}
}let c = new Car('Volkswagen');
c.sound(); // The Volkswagen tooted its horn!

If there is a constructor present in the subclass, it needs to first call super() before using “this”.

Function-based “classes” may also be extended:

function Vehicle (name) {
this.name = name;
}Vehicle.prototype.sound = function () {
console.log(`${this.name} makes a sound.`);
}class Car extends Vehicle{
speak() {
console.log(`The ${this.name} tooted its horn!`);
}
}let c = new Car('Volkswagen');
c.sound(); // The Volkswagen tooted its horn!

Note: classes cannot extend regular objects! If you want to inherit from an object, use Object.setPrototypeOf():

const Vehicle = {
sound() {
console.log(`${this.name} makes a sound.`);
}
};class Car{
constructor(name) {
this.name = name;
}
}let c = new Car('Volkswagen');
c.sound(); // Volkswagen makes a sound.

Species

If you want to return Array objects from an array class MyArray. You can do so with the “species” pattern, which lets you override the default constructors.

If using methods such as map() it’ll return the default constructor. Then you’ll want these methods to return a parent Array object, instead of the MyArray object. Symbol.species lets you do this, like so:

class MyArray extends Array {
// Overwrite species to the parent Array constructor
static get [Symbol.species]() { return Array; }
}let a = new MyArray(1,2,3);
let mapped = a.map(x => x * x);console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true

The ‘Super’ keyword

The super keyword is used to call corresponding methods of super class. This is one advantage over prototype-based inheritance. Let’s see an example:

class Volkswagen { 
constructor(name) {
this.name = name;
}

sound() {
console.log(`${this.name} makes a sound.`);
}
}class Beetle extends Volkswagen {
sound() {
super.sound();
console.log(`${this.name} toots it's horn.`);
}
}let b = new Beetle('Herbie');
b.sound();
// Herbie makes a sound.
// Herbie toots it's horn.

Mix-ins

Mix-ins are templates for classes. An ECMAScript class can only have a single superclass, so multiple inheritance from tooling classes, for example, isn’t possible. The functionality must be provided by the superclass.

A function with a superclass as input and a subclass extending that superclass as output can be used to implement mix-ins in ECMAScript:

let calculatorMixin = Base => class extends Base {
calc() { }
};let randomizerMixin = Base => class extends Base {
randomize() { }
};

A class that uses these mix-ins can then be written like this:

class First { }
class Second extends calculatorMixin(randomizerMixin(First)) {}