Javascript: classes

·

7 min read

In javascript classes use prototype-based inheritance. A class is like a template or a blueprint where you inherit those template properties and methods in all of your objects based on that particular class. This is also known as object-oriented programming. These objects are known as instances of a class. Since ES6 classes can be created using the 'class' keyword which has better syntax and readability. Before that classes are created differently. Let's look at all the ways of creating a class.

Creating classes

classes can be created in the following ways.

  • using factory function

  • using constructor function

  • using the class keyword

  1. Using factory function

    A factory function is a function that creates and returns an object when you call the function. It's just like a regular function. Look at the following example. It uses Object.create() which creates an object with an arbitrary prototype object where the newly created object inherits properties from this arbitrary prototype object.

     function Rectangle(width,length){
     // newly created object inherits from rectangle.methods
     let o = Object.create(Rectangle.methods)
    
     // declare and initialize the unique properties
         o.width = width                                          
         o.length = length
    
     //return the object
         return o
     }
    
     //create a prototype  object and store it as property of rectangle 
     Rectangle.methods = { 
         perimeter() { return 2 * (this.width + this.length)},
         area() { return this.width * this.length}
     }
     let rectangleOne = Rectangle(4,8)
     rectangleOne.width  // => 4
     rectangleOne.length // =>8
     rectangleOne.area() // => 32
     rectangleOne.perimeter() // => 24
    

    In the above example, we created a function called rectangle which accepts two arguments. This function creates an object using object.create(). The object. create() creates and returns a new object which inherits the properties from the first argument of the Object. create() which is also known as a prototype Object. These are not unique and are shared by other objects. Then we define and initialize the properties of the newly created object using the arguments passed. We store the prototype object in the rectangle properties(functions can behave like objects). This way of storing prototype objects is not mandatory, it can be stored elsewhere as well. Finally, we call the function which returns the object.

  2. Using constructor function

    Let's see the same above example using the constructor function.

     function Rectangle(width,height){
         this.width = width
         this.height = height
     }
     Rectangle.prototype = { 
         perimeter() { return 2 * (this.width + this.height)},
         area() { return this.width * this.height}
     }
     let rectangleOne = Rectangle(4,8)
     rectangleOne.width  // => 4
     rectangleOne.length // =>8
     rectangleOne.area() // => 32
     rectangleOne.perimeter() // => 24
    

    Using the constructor function is pretty similar to the factory function. Here the rectangle() function is known as the constructor function. Its work is to initialize the properties of the newly created object. The difference is we need to use the keyword 'new' and the prototype is stored in the prototype property of the constructor function. The newly created object inherits properties from this prototype property of the constructor function. The prototype property is present only in the functions.

    The new keyword creates the new object and invokes the constructor function as the method of the newly created object and returns the object. So "this" value inside the constructor points to the newly created object.

  3. Using class keyword

    The above example can be written using class keyword in the following way.

     class Rectangle{
         constructor(width,height){
             this.width = width
             this.height = height
         }
         area() { 
             return this.width * this.height
         }
         perimeter(){
             return 2 *(this.width + this.height)
         }
     }
    
     let rectangleOne = Rectangle(4,8)
    
     rectangleOne.width  // => 4
     rectangleOne.length // =>8
     rectangleOne.area() // => 32
     rectangleOne.perimeter() // => 24
    
  • class is declared using the class keyword followed by the class name, and class body in curly brackets.

  • The class names are usually declared with the first letter as capital.

  • the class body consists of constructors and methods. Unlike objects, they are not separated by commas.

  • The keyword constructor is used to define the constructor function. This constructor function is used to initialize the instance properties.

  • The above one is a class declaration but they can be in the form of expression as well. Unlike functions, class declarations are not hoisted. So you cannot create an object before the class declaration.

  • Like function expressions, the name is optional for class expressions. Below is a class expression example.

      const square = class { constructor(x){ this.area = x*x}}
      const square1 = new square(4)
      square1.area  // => 16
    

public ,private,Static ,fields(aka property),methods

There are few class properties to write full fledged classes according to their needs.

  1. public and private static methods

  2. public and private static fields

  3. public and private fields

  4. public and private methods

static methods are defined in the class body using the keyword 'static'. These are stored in the class property and not in the class prototype property where the usual methods are stored. Therefore these are not inherited by the instances of the class. These are invoked using the class name. By default, they are public which means they can be invoked outside of the class.

To write a private static method or a private instance method you need to start the method name with "#" which specifies it is private. private methods are not directly accessible outside of the class. You can only invoke it using another public method. The use cases for using private static methods and private instance methods are a lot less I guess.

fields are nothing but properties. A newly created object properties are written inside the constructor. with fields, you can do that outside the constructor. To create a private field the name should start with "#". Private fields should be declared before they can be used anywhere inside the class body. similarly, if you add static before field it is created as class property and is not inherited by the instances of that class.

Private fields are useful if you don't want them to be accessed directly or only want them to be readable. You cannot read it directly by accessing it. It can be read using any public methods defined inside the class.

class Buffer {
    constructor() {
        this.size = 0;
        this.capacity = 4096;
    }
}
// With the new instance field syntax you could instead write:
class Buffer {
    size = 0;
    capacity = 4096;
}
class Rectangle{
    #width  // private field syntax
    #height 
    constructor(width,height){
        this.#width = width
        this.#height = height
    }
    area() { 
        return this.#width * this.#height
    }
    perimeter(){
        return 2 *(this.#width + this.#height)
    }

// static method created as class property
   static diagonal(input){ 
        return Math.sqrt(input.#width*input.#width + input.#height*input.#height)
    }
}


let rectangle1 = new Rectangle(3,4)
Rectangle.diagonal(rectangle1) // => 5
rectangle1.#height // => error cannot access private field outside class

Subclasses

when a class inherits methods of another class is known as a subclass. The class which is inherited by the other class is known as the superclass. The subclass can also have its properties and methods. It can also override the methods of the superclass. Look at the following example.

class Person{
    constructor(firstName,lastName,age){
         this.firstName = firstName
         this.lastName = lastName
         this.age = age
     }
    fullName(){
        return this.firstName + this.lastName
    }
}

class Student extends Person{
    constructor(firstName,LastName,age,grade){
        super(firstName,LastName,age,)
        this.grade = grade
        }
}

let studentOne = new Student("elon","musk",50,9.1);
studentOne.fullName()
studentOne.age
studentOne.grade

we defined a class name Person which initializes some properties and has one method. Then we defined a class named Student which extends the class Person. 'extend" is the keyword used to inherit from other classes. In class Student, we can access the parent class constructor by using 'super()' which initializes the common properties and has its own property 'grade'. As it inherits the methods we can use the fullName() method on Student objects. Using a class keyword is just a syntactic sugar and the underlying inheritance principle which is prototype based is the same as other ways of writing classes.

  1. super()
  • when you define a subclass, the constructor for that class must use super() to invoke the superclass constructor.

  • if you don't define a constructor for the subclass, implicitly a constructor is created and super() is invoked with whatever arguments you passed.

  • you cannot use 'this' in the subclass constructor until the superclass constructor is invoked.