Javascript Classes

In object-oriented programming, a class consists of data and a set of methods that operate on that data. We use the keyword class to create a class in Javascript, as shown below. Every class must have a constructor method. The constructor method is called each time an object initialized.

class MyClass 
{
    constructor() 
    {        
    }
}

 

To create an instance of a class, use the code below:

let a = new MyClass()

 

Classes can contain methods. To be useful, methods should operate on an object's data.

class MyClass 
{
    constructor() 
    {      
    }

    myMethod()
    {
    }
}

 

Class variables, called member variables, are of the form "this.variableName". Member variables do not need to have the keyword "let" in front of them. Member variables are:

class Person 
{
    constructor(forename, surname) 
    {      
        this.forename = forename
        this.surname = surname  
    }

    getFullName()
    {
        return this.forename + " " + this.surname  // member variables globally available to the methods of the class
    }
}

let person = new Person("Alan", "Smith")

/* call a method */
console.log(person.getFullName())

/* read a member variable */
console.log(person.forename)

/* change a member variable */
person.forename = "1234"
console.log(person.forename)

 

Setters and Getters

In the above example, we were able to directly overwrite a member variable. This is bad, as it exposes the data in our objects. In order to stop this, we make us of special set and get methods.

Setter methods allow us to filter content before it overwrites a member variable, as shown below:

class Person
{
    constructor(forename, surname)
    {
        this._forename = forename
        this._surname = surname
    }

    set forename(newForename)
    {
        this._forename = newForename
    }

    set surname(newSurname)
    {
        this._surname = newSurname
    }

    get forename()
    {
        return this._forename
    }

    get surname()
    {
        return this._surname
    }
}

let person = new Person("Alan", "Smith")

/* read a member variable */
console.log(person.forename)

/* change a member variable */
person.forename = "1234"
console.log(person.forename)

Write code to not allow forenames that include numbers or a space.

Setter methods allow us to filter content before it overwrites a member variable. In the example below, we ensure that the forename is at least two characters long.

When using setters, we cannot name the method (for example set forename) to be the same as the member variable name (for example, this.forename). Common coding convenstion is to place an _ in front of the member variable (for example, this._forename).

class Person
{
    constructor(forename, surname)
    {
        this._forename = forename
        this._surname = surname
    }

    set forename(newForename)
    {
        if(newForename.length > 1) // forename must be at least two digits long
        {
            this._forename = newForename
        }
    }
}

let person = new Person("Alan", "Smith")

/* read a member variable */
console.log(person._forename)

/* change a member variable */
person.forename = "I" // the forename will not change, as the input in only one character long
console.log(person._forename)   

/* change a member variable */
person.forename = "Bob"
console.log(person._forename)

 

Placing an '_' in front of each of our member variables means leads to strange code when we want to display a member variable (for example person._forename). We can associate a getter method with each member variable that we wish to give code outside of the class access to.

class Person
{
    constructor(forename, surname)
    {
        this._forename = forename
        this._surname = surname
    }

    set forename(newForename)
    {
        this._forename = newForename
    }   

    get forename()
    {
        return this._forename
    }    
}

let person = new Person("Alan", "Smith")

/* read a virtual member variable */
console.log(person.forename)

Write code to not allow forenames that include numbers or a space.

Getter methods allow us to create virtual member variables. In the example below, we can access the virtual member variable called fullName.

class Person
{
    constructor(forename, surname)
    {
        this._forename = forename
        this._surname = surname
    }

    set forename(newForename)
    {
        this._forename = newForename
    }

    set surname(newSurname)
    {
        this._surname = newSurname
    }

    get forename()
    {
        return this._forename
    }

    get surname()
    {
        return this._surname
    }

    get fullName()
    {
        return this._forename + " " + this._surname  // member variables globally available to the methods of the class
    }
}

let person = new Person("Alan", "Smith")

/* read a virtual member variable */
console.log(person.fullName)

 

A static method defines a property of a class rather than an individual object. To use a static object, you must call in on the class and not on an instance of a class. Static methods are used to create utility functions. They can be called without creating an instance of a class. 

class Person
{
    constructor(forename, surname)
    {
        this._forename = forename
        this._surname = surname
    }

    static staticMethod()
    {
        return "hello world"
    }
}

/* read a virtual member variable */
console.log(Person.staticMethod())  // we are calling the class and not on an instance of a class

 

Classes can inherit the methods from another class. Use the keyword extends to inherit from a parent class.

class Person
{
    constructor(forename, surname)
    {
        this._forename = forename
        this._surname = surname
    }

    set forename(newForename)
    {
        this._forename = newForename
    }

    set surname(newSurname)
    {
        this._surname = newSurname
    }

    get forename()
    {
        return this._forename
    }

    get surname()
    {
        return this._surname
    }

    get fullName()
    {
        return this._forename + " " + this._surname  // member variables globally available to the methods of the class
    }
}



class Student extends Person
{
    constructor(forename, surname, year)
    {
        super(forename, surname) // forename and surname are passed to constructor or Person
        this._year = year
    }

    set year(newYear)
    {
        this._year = newYear
    }

    get year()
    {
        return this._year
    }        
}

let student = new Student("Alan", "Smith", 2)

/* read a member variable from the Student class and the Person class that Student extends from */
console.log("Name: " + student.fullName + "      Year: " + student.year) 

Static variables

Static variables are shared among all instances of a class. Static variables can be used to hold variable or constant data.

Static variables belong to the class rather than the instance of the class. Therefore,

Math.PI is an example of a static constant variable.

// access PI by referencing the class directly

console.log(Math.PI)

 

In most cases, we shall not have a need to create our own static variables (as we usually declare variables or constants in the constructor instead). If you do create a static variable, it must be declared outside of any of the class's methods (as it belongs to the class rather than an instance of the class).

class Employee
{
    static roles = {administrator:1, manager:2, user:3}
    static count
    
    
    constructor()
    {
    }
    
    ...
}

Static Methods

Static methods are shared among all instances of a class. Static methods never access the data of the class, as the data will be different for each instance of the class. Instead, static methods are used to create utility functions that relate to the class.

Static methods belong to the class rather than the instance of the class. Therefore:

Math.abs is an example of a static method.

// access PI by referencing the class directly 
console.log(Math.abs(-10))

Binding

With arrow functions the this keyword represents the object that called the arrow function. Therefore, with arrow functions there is no binding of this.

In the example below, we need to bind the call to displayMessage(), so that the interval will operate on this specific object instance.

class BindExample
{
    constructor(delay, message)
    {
        this.message = message
        setInterval(this.displayMessage.bind(this), delay) // works
    }
                          
    displayMessage = () => console.log(this.message)
}
            
let bindHello = new BindExample(1000, "Hello")
let bindWorld = new BindExample(5000, "World")

We can implement the same functionality using an arrow function, as shown below. In this case, we do not need to bind this, as it refers to the object that called the arrow function.

class BindExample
{
    constructor(delay, message)
    {
        this.message = message
        setInterval( () => this.displayMessage(), delay) // works
    }
                          
    displayMessage = () => console.log(this.message)
}
            
let bindHello = new BindExample(1000, "Hello")
let bindWorld = new BindExample(5000, "World")

 

Note that we cannot embed a function inside the setInterval using a standard function call, as shown below.

class BindExample
{
    constructor(delay, message)
    {
        this.message = message
        setInterval(function(){this.displayMessage.bind(this)}, delay) // Does not work
    }
                          
    displayMessage = () => console.log(this.message)
}
            
let bindHello = new BindExample(1000, "Hello")
let bindWorld = new BindExample(5000, "World")

 

 
<div align="center"><a href="../versionC/index.html" title="DKIT Lecture notes homepage for Derek O&#39; Reilly, Dundalk Institute of Technology (DKIT), Dundalk, County Louth, Ireland. Copyright Derek O&#39; Reilly, DKIT." target="_parent" style='font-size:0;color:white;background-color:white'>&nbsp;</a></div>