Constructor Functions and ES6 Classes

In my previous post, Object Oriented Programming in JavaScript, we discussed objects in JavaScript. We created a model of a car. In this post, we will discuss how to write a constructor function to generate car objects.

How do we create a class or prototype in JavaScript?

What would this look like if we were to write a regular JavaScript function? Set the currentSpeed to 0, and give it a function to accelerate to a given speed.
const createNewCar = (make, model, year, color, name, engine, hasSpare) => {
  let obj = {}
  obj.make = make
  obj.model = model
  obj.year = year
  obj.color = color
  obj.name = name
  obj.engine = engine
  obj.hasSpare = hasSpare
  obj.speed = 0
  obj.updateSpeed = (newSpeed) => {
    let acceleration = newSpeed - obj.speed
    obj.speed = newSpeed
    if (acceleration > 20)
      console.log(`Vrrm vrrm... accelerated by ${acceleration}`)
    else if (acceleration > 0)
      console.log(`Accelerated by ${acceleration}`)
    else if (acceleration < -20)
      console.log(`Screech! Decelerated by ${acceleration}`)
    else if (acceleration < 0)
      console.log(`Decelerated by ${acceleration}`)
    else
      console.log("No Change")
  }
  return obj;
}

Now let's instantiate it. Here is an example of how you could create Belinda. As well as the code that you would use to check her properties and change her speed.
> let belinda = createNewCar("Toyota", "Prius", "2013", "blue", "Belinda", "1.8 L 4-cylinder", true)
  
> belinda.name
> belinda.currentSpeed
> belinda.updateSpeed(50)
> belinda.updateSpeed(30)
> belinda.updateSpeed(30)

With this example, what is the prototype of belinda? We can check the type of belinda with typeof belinda. We can get the prototype with Object.getPrototypeOf(belinda). What do you notice when you type these? Belinda is just a regular Object.

Let's create a car class using ES6.

class Car {
  constructor(make, model, year, color, name, engine, hasSpare) {
    this.make = make
    this.model = model
    this.year = year
    this.color = color
    this.name = name
    this.engine = engine
    this.hasSpare = hasSpare
    this.speed = 0
  }

  makeModelYear() {
    return `${this.year} ${this.make} ${this.model}`
  }

  displayName() {
    return `${this.name}, a ${this.color} ${this.makeModelYear}`
  }

  updateSpeed(newSpeed) {
    let acceleration = newSpeed - this.currentSpeed
    this.currentSpeed = newSpeed
    if (acceleration > 20)
      console.log(`Vrrm vrrm... accelerated by ${acceleration}`)
    else if (acceleration > 0)
      console.log(`Accelerated by ${acceleration}`)
    else if (acceleration < -20)
      console.log(`Screech! Decelerated by ${acceleration}`)
    else if (acceleration < 0)
      console.log(`Decelerated by ${acceleration}`)
    else console.log("No Change")
  }
}
Wanna see how this was done with a Constructor Function before ES6?
function Car(make, model, year, color, name, engine, hasSpare) {
  this.make = make
  this.model = model
  this.year = year
  this.color = color
  this.name = name
  this.engine = engine
  this.hasSpare = hasSpare
  this.speed = 0

  this.makeModelYear = function() {
    return this.year + " " + this.make + " " + this.model
  }

  this.displayName = function() {
    return this.name + ", a " + this.color + " " + this.makeModelYear
  }

  this.updateSpeed = function(newSpeed) {
    var acceleration = newSpeed - this.speed
    this.speed = newSpeed
    if (acceleration > 20) {
      console.log("Vrrm vrrm... accelerated by " + acceleration)
    } else if (acceleration > 0) {
      console.log("Accelerated by " + acceleration)
    } else if (acceleration < -20) {
      console.log("Screech! Decelerated by " + acceleration)
    } else if (acceleration < 0) {
      console.log("Decelerated by " + acceleration)
    } else {
      console.log("No Change")
    }
  }
}

Here you should note that you have no access to the getter function displayName. Instead it is a regular function.


Given this new Car class, how would we instantiate Belinda? And how would we get her display name?
> let belinda = new Car("Toyota", "Prius", "2013", "blue", "Belinda", "1.8 L 4-cylinder", true)
  
> belinda.displayName
> belinda.currentSpeed
> belinda.updateSpeed(50)
> belinda.updateSpeed(30)
> belinda.updateSpeed(30)

What is the new prototype of belinda? Rerun typeof belinda and Object.getPrototypeOf(belinda). What is different this time? What does this tell us about the object belinda? When you check the prototype of belinda, expand the results to see what type of constructor it is. Look at the functions that we have available to us in this constructor.

Notice the getter function is listed in the prototype twice, once as displayCarName: (...) as well as get displayCarName: f displayCarName(). We have access to this getter function without using (). Try accessing it using parentheses: belinda.displayCarName(). What kind of error do we get?

More fun stuff with Classes

What if we want to create a method that will compare the speed of two different cars? There are a few ways to do this.

We could extend the prototype to allow us to compare the instance to another instance.
Car.prototype.compareSpeedTo = function(otherCar) {
  if (otherCar.currentSpeed > this.currentSpeed) {
    return otherCar.displayName
  } else if (this.currentSpeed > otherCar.currentSpeed) {
    return this.displayName
  } else {
    return "They are the same"
  }
}

Since a constructor and a class are just objects, we could also just create a method on the Car object to allow us to compare two different instances.
Car.compareSpeeds = (car1, car2) => {
  if (car1.currentSpeed > car2.currentSpeed) {
    return car1.displayName
  } else if (car2.currentSpeed > car1.currentSpeed) {
    return car2.displayName
  } else {
    return "They are the same"
  }
}

Both of these are perfectly valid ways of doing this. But we can also create a static method right in our class definition. This is similar to class methods (as opposed to instance methods) in other languages (Hello, Ruby!). Here is how we might do that. Note that I'm simplifying the basic properties and constructor to illustrate it.

class Car {
  constructor(name, speed) {
    this.name = name
    this.speed = speed
  }

  static compareSpeeds(car1, car2) {
    if (car1.speed > car2.speed) {
      return car1.name
    } else if (car2.speed > car1.speed) {
      return car2.name
    } else {
      return "They are the same"
    }
  }
}

Now let's see how we compare two cars:

> let belinda = new Car("Belinda", 50)
> let jane = new Car("Jane", 50)
> let margot = new Car("Margot", 40)
> let elissa = new Car("Elissa", 60)

> Car.compareSpeeds(belinda, jane)
They are the same

> Car.compareSpeeds(belinda, margot)
Belinda

> Car.compareSpeeds(belinda, elissa)
Elissa

Summary

Classes and constructor functions give us a way to create objects that have the same prototype, which is basically just a template. These allow us to group objects that have similar characteristics and that require similar functions. In this post, we have seen how to:

  • create a constructor, through both constructor functions and through ES6's class pattern.
  • instantiate objects with this constructor or class
  • access both the properties on these instances
  • call methods on these instances
  • create class level methods
  • call class level methods