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

Object Oriented Programming in JavaScript

OOP

What is object-oriented programming(OOP)?

According to Microsoft Developer Network,

The basic idea of OOP is that we use objects to model real world things that we want to represent inside our programs,  and/or provide a simple way to access functionality that would otherwise be hard or impossible to make use of.

An object is a collection of properties and functions that can store data as well as send messages to and receive messages from other objects. It is highly modular, making it both flexible as well as maintainable.

Objects can contain related data and code, which represent information about the thing you are trying to model, and functionality or behavior that you want it to have. Object data (and often, functions too) can be stored neatly (the official word is encapsulated) inside an object package (which can be given a specific name to refer to, which is sometimes called a namespace), making it easy to structure and access; objects are also commonly used as data stores that can be easily sent across the network.

What are some benefits of object oriented programming?

  • Encapsulation: All of the data and functions are stored within the object itself.
  • Namespacing: The functions that we define are namespaced to this object. This is easier to demonstrate when we use constructors to define new objects.
  • Data stores: Objects are easy and efficient ways to store data that can be manipulated or passed to different parts of a program.

Run the following code in the console:

> const obj = { a: 1, b: 3 }

> obj

Now expand the result. You should see what is contained within this object literal. We have the data store (a and b) as well as the functions that are available to it. This is what we refer to as encapsulation. Expand __proto__ to see these functions. Now try running some of these methods on your object to see the results:

> obj.toString()

> obj.valueOf()

Object Template

Let’s look at an example of some real world objects. In my day job, I work for a company called RepairPal that connects consumers to auto mechanics that are reputable. We have objects to represent cars.

What are some attributes you might expect for a car?

Let’s give it the following properties:
  • a carMake
  • a carModel
  • a year
  • a color
  • a name (everyone names their car, right?)
  • an engineSize
  • a spareTire - a boolean (T/F) if the car has a spare tire
  • a currentSpeed

Now that we have a basic template for a car (with just the data), what would an instance of that car look like. Remember, an instance of a model or template has the structure of that object but with actual data filled in. This is an abstraction of our real world object. We are just taking the relevant information of it. Try to come up with an instance of your own car.

Once you give it a shot, compare it to the attributes for my car.


Here is what my car would look like:
  • carMake: "Toyota",
  • carModel: "Prius"
  • year: "2013"
  • color: "blue"
  • name: "Honey"
  • engineSize: "1.8 L 4-cylinder"
  • hasSpareTire: true
  • currentSpeed: 0
Notice that the data has multiple types: strings, booleans, and numbers.

Summary

Now we have a general idea of what objects are, we need to see how to create them in JavaScript. Read my next blog post about Constructors and ES6 Classes for more information on how to create functions to build objects and how to instantiate them.