Classes

If functions are the verbs of computer programming, then classes let us define the nouns.

Table of Contents

  1. Object Literals
  2. Classes are New(ish)
  3. Defining a Class
  4. Instantiating a Class
  5. Accessing Internal State and Methods
  6. Static Properties
  7. Static Methods
  8. Sub-Classing with Extends
  9. Method Overriding
  10. Properties Are Public

Object Literals

In Javascript we can create objects without classes using the object literal syntax.

We can even attach functions to these objects.

let mySpaceShip = {
  fuelAvailable: 1000,
  move: function () {
    this.fuelAvailable--;
  },
};

mySpaceShip.move(); // The fuelAvailable property is now 999.

⚡ Warning:

Arrow functions can’t be used here as they don’t bind to the object’s this property.

Classes are New(ish)

For the first twenty years Javascript had no class keyword. Writing object-oriented code was still possible, but we had to use object literals (as seen above) and prototypal inheritance.

Classes were added to Javascript in 2015 as part of version 6 of the ECMAScript standard (ES6). They are still a moving target with new features and refinements being added today.

Defining a Class

Let’s create a SpaceShip class. We’ll give it a constructor to set our initial fuel level and a move method to decrease the fuel amount.

Object properties must be attached to this, which is an object’s reference to itself.

class SpaceShip {
  // Constructors are always named: constructor
  constructor(initialFuel) {
    this.fuelAvailable = initialFuel;
  }
  // Methods can be defined with optional parameters.
  move() {
    this.fuelAvailable--;
  }
}

🎵 Note:

We don’t use the function keyword to define methods in our classes.

Instantiating a Class

We can now instantiate multiple objects of type SpaceShip and call their move methods.

let myShip = new SpaceShip(1000);
let yourShip = new SpaceShip(5000);

myShip.move(); // myShip.fuelAvailable is now 999.
yourShip.move(); // yourShip.fuelAvailable is now 4999.

Accessing Internal State and Methods

The internal variables of a class are called properties or sometimes instance variables. Within a class these properties are always accessed by prepending this. to the property name.

The functions associated with a class are called methods. As we saw above, when calling a method on an object we use the dot operator:

let myShip = new SpaceShip(1000);
myShip.move(); // Calling the move() method on the myShip object of type SpaceShip.

Within a class one method can call another method by prepending this. to the method name. Look at how the move() method calls the debugPrint() method in the example below:

class SpaceShip {
  constructor(initialFuel) {
    this.fuelAvailable = initialFuel;
  }
  
  move() {
    this.fuelAvailable--;
    this.debugPrint(); // Calling another method.
  }
  
  debugPrint() {
    console.log("The ship has " + this.fuelAvailable + " remaining.");
  }
}

Static Properties

It can sometimes be helpful to store data directly in a class, rather than in the instances of the class.

class Goat {
  static goatsMade = 0;
  constructor(name) {
    this.name = name;
    Goat.goatsMade++;
  }
}

console.log(Goat.goatsMade); // 0
let myGoat = new Goat("Bert");
console.log(Goat.goatsMade); // 1

Static Methods

Attaching helper methods or factories to the class can also be helpful.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  static distance(a, b) {
    const dx = a.x - b.x;
    const dy = a.y - b.y;
    return Math.hypot(dx, dy); // Use built-in math helper to find the distance.
  }
}

let pointOne = new Point(3, 9);
let pointTwo = new Point(14, 2);
let distanceBetweenPoints = Point.distance(pointOne, pointTwo);

Sub-Classing with Extends

We can use the extends keyword to create a subclass, along with super() to call the parent’s constructor:

class Student {
  constructor(name) {
    this.name = name;
  }
}

class CollegeStudent extends Student {
  constructor(name, debt) {
    super(name);
    this.debt = debt;
  }
}

let joe = new Student("Joe");
let daisy = new CollegeStudent("Daisy", 2342);

Method Overriding

Subclasses can override the methods of their parent classes by defining a method of the same name. The parent’s version of the method can be executed using super.methodName().

class Student {
  constructor(name) {
    this.name = name;
  }
  toString() {
    return `Name: ${this.name}`;
  }
}

class CollegeStudent extends Student {
  constructor(name, debt) {
    super(name);
    this.debt = debt;
  }
  toString() {
    return super.toString() + `, Debt: ${this.debt}`;
  }
}

let joe = new Student("Joe");
let daisy = new CollegeStudent("Daisy", 2342);
console.log(joe.toString()); // Name: Joe
console.log(daisy.toString()); // Name: Daisy, Debt: 2342

Properties Are Public

One of the key tenents of object-oriented programming is that of “data hiding” or “encapsulation”. Unfortunately we don’t get that in Javascript objects, yet. Currently all properties attached to this are publicly gettable and settable.

Using the daisy object of type CollegeStudent from above:

console.log(daisy.debt);
daisy.debt = 0;
console.log(`That was easy. Daisy now owes ${daisy.debt} dollars`);

Private properties prefixed by the # symbol are being added the ECMAScript specification but aren’t yet widely supported.