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 typeof belinda
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