What is it really?
Problem
Only some of them can charge
A company has a fleet of regular and electric cars. Every evening the electric ones need to be charged. ElectricCar inherits from Car and adds charge(), so they are all in one array:
const fleet = [ new Car("Skoda"), new ElectricCar("Tesla", 35), new Car("Dacia"), new ElectricCar("Nissan", 60),];for (const c of fleet) { c.charge();}The program starts and stops at the very first car, "Skoda": TypeError: c.charge is not a function. It never gets to the electric ones.
A regular car really cannot be charged. You have to ask each car: "are you electric?"
The idea
Ask the object what it is
for (const c of fleet) { if (c instanceof ElectricCar) { c.charge(); console.log(`${c.brand}: charged to ${c.battery}%`); }}Tesla: charged to 100%Nissan: charged to 100%c instanceof ElectricCar asks the object c points to: "are you an ElectricCar?" and gives true or false. This is called a type check.
You do not need a new variable. In JavaScript variables have no type, so there is nothing to convert: once the check is true, c points to an electric car and c.charge() works. The cars stay the same.
for (const c of fleet) { if (c instanceof ElectricCar) { c.charge(); console.log(`${c.brand}: charged to ${c.battery}%`); }}Step by step, in words
- fleet is an array: 2 regular and 2 electric cars.
- c points to Skoda: instanceof says "no". The body of the if is skipped.
- c points to Tesla: instanceof says "yes". No new variable is needed: c.charge() charges the same car.
- c points to Dacia: instanceof says "no". The body of the if is skipped.
- c points to Nissan: instanceof says "yes". No new variable is needed: c.charge() charges the same car.
- The cars do not change. The check only asks the object what it is.
Try it
Is it a car?
What will the program print?
const c = new ElectricCar("Tesla", 35);const d = new Car("Skoda");console.log(c instanceof Car);console.log(c instanceof ElectricCar);console.log(d instanceof ElectricCar);An electric car is also a car, so the first check is true. The check does not ask "is it exactly a Car?" but "can this object be used as a Car?". The regular car d is not electric, so the last check is false.
The idea
Other forms you will run into
Besides instanceof you will run into a few more ways to ask an object:
const c = fleet[1];console.log(c.constructor.name);console.log(c.constructor === Car);console.log(c instanceof Car);console.log(typeof c.charge === "function", typeof fleet[0].charge === "function");console.log(typeof 42, typeof "Tesla", typeof true, typeof c);ElectricCarfalsetruetrue falsenumber string boolean objectc.constructoris the class the object was created from, andc.constructor.nameis its name.c.constructor === Carasks "is it exactly aCar?" and for the electric car it isfalse, whilec instanceof Caristrue. You almost always wantinstanceof: it accepts child classes too.typeof c.charge === "function"does not ask about the class, only whether the object has such a method. This is the contract check from the "Interfaces" lesson: any object withcharge()passes.typeofgives the kind of a value as text:"number","string","boolean","function". For every object it is"object", so it does not tell you the class. For the class you useinstanceof.
More precisely, if you are curious
- The check is always false for
null:null instanceof Carisfalse. So after a successfulinstanceofyou never havenull. - You cannot use
instanceofto ask about a contract: a contract is only method names, and there is no class to compare against. For that you usetypeof a.swim === "function".
Try it
Find the bug
The program counts the regular and the electric cars. The fleet has two of each, but it prints:
Regular: 4, electric: 0Which line is the bug on?
Pick the line with the bug.
Every electric car is also a Car, so the first check is true for all four cars. The program never gets to the second one. Check the more specific type first: if it is an ElectricCar, count it as electric, otherwise as regular.
The idea
A check or a method?
You want to print what each car runs on. A type check can do it:
for (const c of fleet) { if (c instanceof ElectricCar) { console.log(`${c.brand}: electricity`); } else { console.log(`${c.brand}: gasoline`); }}But this is the salary with an if per role from the lesson on polymorphism. Every car has an energy source, just a different one. That is a job for a method in Car, overridden in ElectricCar:
// in Carenergy() { return "gasoline";}
// in ElectricCarenergy() { return "electricity";}for (const c of fleet) { console.log(`${c.brand}: ${c.energy()}`);}Skoda: gasolineTesla: electricityDacia: gasolineNissan: electricityTomorrow a hybrid car comes along. With the method, you write a new class. With the checks, you hunt down every if in the program.
The rule:
- Every object does it, each in its own way: a method in the parent, and overriding.
- Only some objects can do it (charging, pedaling): a type check.
Try it
What is missing?
for (const c of fleet) {
if (c ElectricCar) {
c.charge();
}
}instanceof: it checks whether c points to an ElectricCar. If it does, c.charge() works.In short
The four pillars of OOP
With this lesson you have finished the modules "Classes and objects" and "Inheritance and polymorphism". Object-oriented programming rests on four ideas. You already use all of them:
- Encapsulation. The object's data is hidden, and from outside you reach it only through methods that guard the rules.
Accountkeeps the balance #private and offers onlydeposit()andwithdraw(). Encapsulation - Inheritance. What is shared sits in one place, the parent, and the child adds its own.
Manageris anEmployeewith a bonus. Inheritance - Polymorphism. One call, different behavior depending on the object.
e.monthlyPay()in one loop works out the pay of an employee, a manager and an intern. Polymorphism - Abstraction. You say what the object must be able to do, and each class decides how. The abstract
Shapepromises an area, the payment contract promises an amount to pay. Abstract classes, Interfaces
Exercise
What is it really?
Solve these in your editor.
1. Parking lot. An array of regular and family cars (FamilyCar inherits from SimpleCar). For each one, print its type. Watch the order of the checks.
This is a SimpleCarThis is a FamilyCarThis is a SimpleCar2. Dances. An array of regular and breakdance moves. For the breakdance moves, also print the complexity. Only BreakDanceMove has it, so ask for it after the check.
moonwalk: DanceMovewindmill: BreakDanceMove, complexity 8salsa: DanceMove3. Fleet. Take Car and ElectricCar from the exercise in the "Inheritance" lesson. In an array, put a "Skoda" with 40 L, a "Tesla" with efficiency 5, a "Dacia" with 5 L and a "Nissan" with efficiency 4. Each one drives 100 km: the electric ones with driveElectric, the rest with drive. Then print the kilometers and the fuel or the battery.
Skoda: 100 km, fuel 30 LTesla: 100 km, battery 80%Not enough fuel for 100 kmDacia: 0 km, fuel 5 LNissan: 100 km, battery 75%4. Zoo. An abstract class Animal with sound(), and child classes Cat, Dog and Fish. Only Fish also has swim(): it fulfills the "can swim" contract. In a loop over the animals, call each one's sound, and for the ones that can swim, also call swim(). The contract has no class, so the check uses typeof.
Meow, meow!Woof, woof!The fish is silent.The fish swims.More tasks
- Pedals. In the exercise from the "Abstract classes" lesson,
garage[0].pedal()stopped the program, because there is no bicycle there. Fix it with a type check: in the loop over the vehicles, every vehicle starts, and only the bicycle also pedals. - Payments by kind. From the "Interfaces" lesson: the payments array holds employees and invoices. Print the total for salaries and the total for invoices separately. For Ivan, Maria and the two invoices: €5,500 in salaries and €2,140 in invoices.
Test