JavaScript Classes (With Examples)

JavaScript Classes (With Examples)

Learn About Classes in JavaScript

JavaScript classes were 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 just like Functions

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

Class Declarations

Let's 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(); // ReferenceError

class Image{}

Class Expressions

A 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 classes 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 superclass (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 transpire 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 ensures that your classes’ users can’t depend on internals, which may change with version changes.

Note: Private fields cannot be created later through an 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.specieslets 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 the superclass. This is one advantage over a 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 inheritances 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)) { ...

We’ve taken a deep dive into JavaScript classes including class declarations, class expressions, constructors, instance properties, field declarations, extends, species, super, and mix-ins.

Conclusion

If you liked this blog post, follow me on Twitter where I post daily about Tech related things! Buy Me A Coffee If you enjoyed this article & would like to leave a tip — click here

🌎 Let's Connect

Did you find this article valuable?

Support Richard Rembert by becoming a sponsor. Any amount is appreciated!