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
andset
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:-
this.color
is used to access getter and setter methodsthis._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.