Introduction to es6 class

Overview

As you might already know javascript is a prototype-based language. It was not designed to object-oriented but as the javascript was getting popular there were different libraries which were trying to extend javascript to be class based. So ES6 introduced class in javascript.

According to MDN:

classes are primarily syntactical sugar over js’s existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

Before diving deep into the javascript class, let us first how different object-oriented concepts were handled through the prototype.

Before ES6.

Functions were used to replicate class.

Declaring properties

//Creating new function
function individual(name, age){
    this.name = name;
    this.age = age;
}

Calling the function as class

let person1 = new individual('prashant', 23);
let person2 = new individual('yogesh', 24);

Declaring methods

//By declaring method inside the function
function individual(name, age){
    this.name = name;
    this.age = age;
    this.details = function(){
       return this.name + ' is ' + this.age + ' years old.';
    }
}

//By declaring the method through prototype
individual.prototype.details = function(){
   return this.name + ' is ' + this.age + ' years old.';
};

//Calling the function
let person1 = new individual('prashant', 23);
console.log(person1.details());

//"prashant is 23 years old."

After ES6.

There are two ways of declaring a class
1) Class declaration.
2) Class expression.

Class Declaration.

//declaring class
class Individual{
    
    //declaring properties inside constructore
    constructor(name, age){
       this.name = name;
       this.age = age;
    }
    
    //declaring display method
    details(){
      return `${this.name} is ${this.age} years old.`;
    }
}
//creating a person object from individual class
let person = new Individual('prashant', 23);

//calling the display method
console.log(person.details());
//"prashant is 23 years old."

Class Expression.

We can assing the class expression to the variables.
1) Unnamed class expression
2) Named class expression

//unnamed class expression
const Person = class{
    
    //declaring properties inside constructore
    constructor(name, age){
       this.name = name;
       this.age = age;
    }
    
    //declaring display method
    details(){
      return `${this.name} is ${this.age} years old.`;
    }
}

//named class expression
const Person = class Individual{
    
    //declaring properties inside constructore
    constructor(name, age){
       this.name = name;
       this.age = age;
    }
    
    //declaring display method
    details(){
      return `${this.name} is ${this.age} years old.`;
    }
}
//creating a person object from individual class
let person1 = new Person('prashant', 23);

//calling the display method
console.log(person1.details());
//"prashant is 23 years old."

constructor() should be decalared only once.

As we can see class works similar to the prototype based approach. But there are many advantages of using classes.

  • Unlike functions, class declarations are not hoisted. Which means variables declared inside are not accessible outside. They remain in temporal dead zone just like let until execution reaches the declaration.
  • Code inside class runs in strict mode.
  • Methods declared inside class are non-enumerable. Unlike prototype based where we had to use object.defineProperty() to make method non-enumerable.
  • Calling the class constructor without new keyword will throw error. Also all the methods decalared inside class lack the [[construct]] method and calling them with new keyword will result in error.
  • Declaring the method name same as class name inside class will result in error.

Static methods

Static methods are only accessible by parents (class), derived child or instance of the class cannot access them.

class Person {
   
    //declaring properties
    constructor(name, age){
      this.name = name;
      this.age = age;
    }
   
    //declaring method
    //accessible by all
    details(){
      console.log(`${this.name} is ${this.age} years old.`);
    }
     
    //static method
    //accessible only my parent
    static display(){
      console.log(`I am only accessible by parent.`);
    }
}
Input:
let person1 = new Person('prashant', 23);
person1.details();
Person.display();
person1.display();

Output:
"prashant is 23 years old."
"I am only accessible by parent."
Uncaught TypeError: person1.display is not a function

Accessor Properties

There are two accessor properties which we can use with class.
1) Set:- To set the value of any property.
2) Get:- To get the value of any property.

class Person {
   
    //declaring properties
    constructor(name, age){
      this.name = name;
      this.age = age;
    }
   
    //Set name
    set nickName(name){
      this.name = name;
    }
   
    //get name
    get nickName(){
       return `your name is ${this.name}`
    }
}
Input:
let person1 = new Person('Prashant Yadav', 23);
console.log(person1.nickName);
//set the new nickName
person1.nickName = 'Golu';  
console.log(person1.nickName);

Output:
"your name is Prashant Yadav"
"your name is Golu"

Computed method names

Class methods and Accessor properties can computed names, That means their name can be provided at runtime.

let myMethod = 'displayDetail';

class Person {
   
    //declaring properties
    constructor(name, age){
      this.name = name;
      this.age = age;
    }
   
    //declaring a method with the computed name
    [myMethod](){
      return `${this.name} is ${this.age} years old.`;
    }
}
Input:
let person = new Person('Prashant Yadav', 23);
console.log(person.displayDetail());

Output:
"Prashant Yadav is 23 years old."

Accessor properties can also use computed methods.

let myMethod = 'differentName';
class Person {
   
    //declaring properties
    constructor(name, age){
      this.name = name;
      this.age = age;
    }
   
    //Set name
    set [myMethod ](name){
      this.name = name;
    }
   
    //get name
    get [myMethod ](){
       return `your name is ${this.name}`
    }
}
Input:
let person1 = new Person('Prashant Yadav', 23);
console.log(person1.differentName);
//set the new differentName
person1.differentName = 'Golu';  
console.log(person1.differentName);

Output:
"your name is Prashant Yadav"
"your name is Golu"

Inheritance

Before ES6, implementing inherintance was a tedious process with prototype. Proper inheritance required multiple steps.

 function Rectangle(width, height){
      this.width = width;
      this.height = height;
  }

  //declaring method to calculate area;
  Rectangle.prototype.getArea = function(){
      return this.height * this.width;
  }

  //Extending rectangle to use it as square
  //using call, apply, bind
  function Square(length){
     Rectangle.call(this, length, length);
  }

  //Extending methods
  Square.prototype = Object.create(Rectangle.prototype, {
    constructor: {
        value: Square,
        enumerable: true,
        writable: true,
        configurable: true
    }
});
var square = new Square(3);

console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true

After ES6, we can use extends keyword to achieve the inheritance.

//Declaring the rectangle class
class Rectangle {
    //intialize the properties
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
    
    //Method to calculate the area
    getArea() {
        return this.length * this.width;
    }
}

//Extend the Square to use Rectangle
class Square extends Rectangle {
    constructor(length) {

        // same as Rectangle.call(this, length, length)
        super(length, length);
    }
}
const square = new Square(3);

console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true

Points to remember while inheriting the another class.

  • If you inherit any class and declare the constructor() method inside it use must declare super() method as well inside the constructor(). Failing to do so will result in error.
  • If there are any static methods in the parent class then they are also accessible to the inherited class.
  • new.target can be used to check the current instance of the class and where it is getting called from.

Derived class form expression

One of the most powerful features of ES6 class is the ability to derive from a class from an expression.
You can use extends with any expression as long as the expression resolves to a function with [[Construct]] and a prototype.

//A function declaration
function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

//Adding method to the function
Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

//Extending the rectangle function
class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }
}
Input:
let x = new Square(3);
console.log(x.getArea());               // 9
console.log(x instanceof Rectangle);    // true

With this powerful feature, we can inherit the inbuilt functions as well. The only clause is that they should have [[construct]] method and a prototype.

Extending the Array

class MyArray extends Array {
    // empty
}

var colors = new MyArray();
colors[0] = "red";
console.log(colors.length); // 1

colors.length = 0;
console.log(colors[0]); // undefined

MyArray inherits directly from the Array. As Array has [[construct]] method as well as a prototype.

class Country extends Array {
  // use rest operator to grab all the persons
  constructor(name, ...persons){
    //pass all the students to the array
    super(...persons);
    this.country = country;
  }
  
  //Method to add new person
  add(person){
    this.push(person);
  }
}
Input:
const myCountry = new Country('India', 
  {name: "Prashant", age: 23},
  {name: "Yogesh", age: 24},
  {name: "Pranav", age: 23},
  {name: "Sachin", age: 23},
);

myCountry.add({name: "Aditya", age: 24});
console.log(myCountry[4]);

//loop the data
for(const persons of myCountry) {
  console.log(persons);
}

Output:
//myCountry[4]
Object {
  age: 24,
  name: "Aditya"
}

//Loop
Object {
  age: 23,
  name: "Prashant"
}
Object {
  age: 24,
  name: "Yogesh"
}
Object {
  age: 23,
  name: "Pranav"
}
Object {
  age: 23,
  name: "Sachin"
}
Object {
  age: 24,
  name: "Aditya"
}

Comments

  • There is typo in the example above with the topic Static Methods

    person.display()

    instead of

    Person.display()

Leave a Reply

Your email address will not be published. Required fields are marked *