Classes in JavaScript Classes in JavaScript

In this tutorial, we’ll learn how to create classes in JavaScript.

Class Declaration

Let’s look at the example of creating class in JavaScript using function constructor and class keyword:

// ES5 Function Constructor
function Car(brand, color, price) {
  this.brand = brand;
  this.color = color;
  this.price = price;
}

// ES6 Class
class Car {
  constructor(brand, color, price) {
    this.brand = brand;
    this.color = color;
    this.price = price;
  }
}

Please note that class is a type of function, so we use that to replace function. In that sense, both ways of creating class are pretty much same.

We can make our code even more shorter like this:

class Car {
  constructor(brand, color, price) {
    Object.assign(this, { brand, color, price});
  }
}

Methods

Let’s add some methods to our Car class

  • Getter Setter method (instance method) is called from the instance of the class. They are defined using get and set keywords to get and set properties respectively.
  • Prototype method (instance method) is called from the instance of the class. They are used to access instance properties and perform some operations on them.
  • Static method (class method) is called directly from the class. They are defined using static keyword and often used to create utility functions.
class Car {
  constructor(brand, color, price) {
    this._brand = brand;
    this._color = color;
    this._price = price;
  }

  // getter method
  get color(){
    return `color is ${this._color.toUpperCase()}`;
  }

  // setter method
  set color(newColor){
    this._color = newColor;
  }

  // prototype method
  drive(){
    return `driving ${this._brand} ${this._color} color car`;
  }

  // static method
  static compareCars(car1, car2){
    return `${car2._brand} is ${(car1._price > car2._price) ? "cheaper" : "costlier"} then ${car1._brand}`
  }
}

Let’s create some objects using Car class and call their getter, setter, prototype and static methods

let redToyotaCar = new Car("Toyota", "red", 500000);

console.log(redToyotaCar);  
// prints Car {_brand: "Toyota", _color: "red", _price: 500000}

console.log(redToyotaCar.color);  
// (getter method)
// prints 'color is RED'

console.log(redToyotaCar.drive());  
// (prototype method)
// prints 'driving Toyota red color car'

redToyotaCar.color = "blue";  
// (setter method)
// set color to blue

console.log(redToyotaCar.drive()); 
// (prototype method)
// prints 'driving Toyota green color car'

let blackAudiCar = new Car("Audi", "black", 900000);
console.log(Car.compareCars(redToyotaCar, blackAudiCar));  
// (static method)
// prints 'Audi is costlier then Toyota'

console.log(redToyotaCar.color); 
// prints 'color is BLUE'

In our class above we have a getter and setter for our color property. We use _ convention to create a backing field to store our color property. Without this every time get or set is called it would cause a stack overflow. The get would be called and which would cause the get to be called again over and over creating an infinite loop.

VM172:12 Uncaught RangeError: Maximum call stack size exceeded
    at Car.set color [as color] (<anonymous>:12:16)
    at Car.set color [as color] (<anonymous>:12:16)
    at Car.set color [as color] (<anonymous>:12:16)
    at Car.set color [as color] (<anonymous>:12:16)
    at Car.set color [as color] (<anonymous>:12:16)
    at Car.set color [as color] (<anonymous>:12:16)
    at Car.set color [as color] (<anonymous>:12:16)
    at Car.set color [as color] (<anonymous>:12:16)
    at Car.set color [as color] (<anonymous>:12:16)
    at Car.set color [as color] (<anonymous>:12:16)

Inheritance

Let’s say we want to create a Toyota subclass from Car class and add some additional fields like “model” and “make”.

class Toyota extends Car {
    constructor(color, price, model, make){
        super("Toyota", color, price);
        Object.assign(this, {model, make});
    }
    drive(){
        return `${super.drive()} made in ${this.make}`;
    }
}

Let’s create some objects from Toyota subclass

let toyotaCamery = new Toyota("red", 800000, "Camary", 2010);

console.log(toyotaCamery);
// prints Toyota {_brand: "Toyota", _color: "red", _price: 800000, model: "Camary", make: 2010}

console.log(toyotaCamery.color);
// prints 'color is RED'

console.log(toyotaCamery.drive());
// prints 'driving Toyota red color car made in 2010'

We see that creating a subclass using ES6 class keyword is quite handy and easy.