Classes in JavaScript Classes in JavaScript

Page content

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}`
  }
}

Examples

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.color); 
// (getter method)
// prints 'color is BLUE'

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

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

More about Get and Set

In the class Car, we created get and set methods with name color:-

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

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

and we call these getter and setter methods using this.color:-

console.log(this.color);  
// (call getter method)

this.color = "blue";  
// (call setter method)

You also see that we have created a property _color, which is initialized inside constructor. Let’s see the difference between these two:-

  1. this.color is used to access getter and setter methods
  2. this._color is used to access _color property which is initialized inside constructor.

If we use the same color property in constructor as well, then it will look something like this:-

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()}`;  
    //${this.color.toUpperCase()} will call get method again, cause recursive loop
  }

  // setter method
  set color(newColor){
    this.color = newColor;
    // this.color will call get method again, cause recursive loop
  }
}

In such case, when you call getter or setter methods using this.color and since we are accessing this.color again inside these methods, getter method will be called recursively and cause stack overflow:-

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)

To avoid this, make sure you are not accessing the property with same name as getter or setter method name. You can follow any naming convention, one of the example is to add get and set suffix in getter setter method names:-

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

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

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

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.