0.4.4: Classes

Learning Objectives

  1. Understand the motivation behind JavaScript classes and object-oriented programming (OOP)

  2. Understand how to use JavaScript class syntax to create and use classes

  3. Understand how class inheritance works, how to use super to call parent class constructor

Introduction

The following is an example of a JavaScript class that represents cars. For simplicity, these cars only travel 1 unit of distance per trip.

class Car {
  // Define class properties in a constructor method
  constructor(colour) {
    this.colour = color;
    this.odometer = 0;
  }

  // Define class methods within the class block
  drive() {
    this.odometer += 1;
  }
}

// Create new "instances" of classes with the "new" keyword
const whiteCar = new Car("white");
const blackCar = new Car("black");

// Call class methods on instances of the class
whiteCar.drive();
blackCar.drive();
blackCar.drive();

// Retrieve class properties as we would with JS Objects
console.log(whiteCar.odometer); // 1
console.log(blackCar.odometer); // 2

JavaScript classes are templates for entities we may wish to manipulate as a unit in our apps. Classes are part of a broader computer science concept called object-oriented programming, also known as OOP. Classes are optional in JavaScript but mandatory in languages such as Java.

Without classes

Without classes, we might store data in JavaScript Objects and manipulate them with helper functions.

const whiteCar = {
  colour: "white",
  odometer: 0,
};

const blackCar = {
  colour: "black",
  odometer: 0,
};

const drive = (car) => {
  car.odometer += 1;
};

To reduce redundancy in object creation, we could write a helper function like createCar to generate our objects.

const createCar = (colour) => {
  return {
    colour: colour,
    odometer: 0,
  };
};

const drive = (car) => {
  car.odometer += 1;
};

const whiteCar = createCar("white");
const blackCar = createCar("black");

With classes

JavaScript classes encapsulate the entity properties (colour and odometer above), entity creation method (createCar above) and any entity helper methods (drive above) within a single unit, i.e. class. This helps us organise our code by thinking of a Car as a single entity, instead of an entity spread across disparate objects and helper functions.

class Car {
  // Define class properties in a constructor method
  constructor(colour) {
    this.colour = color;
    this.odometer = 0;
  }

  // Define class methods within the class block
  drive() {
    this.odometer += 1;
  }
}

// Create new "instances" of classes with the "new" keyword
const whiteCar = new Car("white");
const blackCar = new Car("black");

We can represent many common app entities as classes, for example users.

class User {
  constructor(name, email, password) {
    this.name = name;
    this.email = email;
    // Always only store hashed passwords to mitigate problems if data stolen
    this.passwordHash = hash(password);
  }

  changePassword(newPassword) {
    this.passwordHash = hash(newPassword);
  }
}

const joe = new User("Joe", "joe@joe.com", "joerox");
const amy = new User("Amy", "amy@amy.com", "amyrox");

Naming convention: Classes are typically named with UpperCamelCase. Instances are typically named with lowerCamelCase.

Class inheritance

One of the most valuable features of OOP and classes is "inheritance". Inheritance allows classes to "inherit" properties and methods from one another by forming "parent-child" relationships. For example, if I wanted to create classes for Bicycle and Car vehicle types, both of which have a speed property and the same calcTravelTime method that depends on speed, I can define how we store the speed property and define the calcTravelTime method in a parent class Vehicle and have Bicycle and Car classes inherit from Vehicle to avoid repeating code.

class-example.js
class Vehicle {
  constructor(speed) {
    this.speed = speed;
  }
  
  calcTravelTime(distance) {
    return distance / this.speed;
  }
}

/*
 * Bicycle and Car classes "inherit" properties and methods from Vehicle class
 */

class Bicycle extends Vehicle {
  constructor(speed) {
    // The super keyword runs the constructor of the parent class
    super(speed);
  }
}

class Car extends Vehicle {
  constructor(speed) {
    super(speed);
  }
}

// Declare new Bicycle and Car instances
const bicycle = new Bicycle(10);
const car = new Car (100);

// Even though we did not define calcTravelTime in Bicycle and Car, they have the method
console.log(bicycle.calcTravelTime(100)); // 
console.log(car.calcTravelTime(100));
  1. Notice we use the keyword extends to specify inheritance in JavaScript

  2. Notice we use the keyword super in constructor methods of child classes Bicycle and Car to call the constructor method of their parent, Vehicle.

Try creating a local file class-example.js with the above code and running it with node class-example.js, playing around with its features to understand how classes work!