What polymorphism is
Polymorphism comes from Greek words meaning "many forms". In code it means that a single call, such as animal.speak(), does different things depending on which kind of object receives it. You write the call once, and the correct override runs automatically.
This builds on the previous two lessons. Suppose Dog and Cat both extend Animal and each overrides speak(). Any code that works with animals can call speak() without caring which animal it holds.
Why it matters
Consider a loop that makes every animal in a list speak:
function chorus(animals) {
for (let a of animals) {
a.speak();
}
}
This function works for every kind of animal that exists today and for kinds that don't exist yet. Add a Cow class tomorrow with its own speak(), and chorus prints Moo for it without a single edit. The code keeps working as the set of classes grows. Without polymorphism you would need a chain of checks, such as "if it is a dog do this, else if it is a cat do that", and each new class would mean changing that chain.
How JavaScript decides which method runs
When you write a.speak(), JavaScript looks for speak on the actual object in a, then on its class, then up through the parent classes, and runs the first version it finds. This happens at the moment of the call, so the actual class of the object decides the result. A Dog finds Dog.speak first, and a Cat finds Cat.speak first.
Statically typed languages separate the declared type of a variable (the static type) from the type of the object it holds at runtime (the dynamic type), and dispatch on the dynamic type. JavaScript variables have no declared type at all, so there is only the object itself. In JavaScript, methods are looked up this way by default, and you need no extra keyword such as virtual.
If a subclass doesn't override the method, the parent's version runs. With class Fish extends Animal {}, new Fish().speak() prints the base Animal text, which is ... in this lesson.
Polymorphism without inheritance
JavaScript doesn't require a shared parent class. The call only needs the object to have a method of that name. An object literal works too:
let duck = { speak() { console.log("Quack"); } };
for (let x of [new Dog(), duck]) {
x.speak(); // Woof, then Quack
}
This style is often called duck typing: if it has a speak method, it can be used where a speaker is expected. The cost is that a mistake shows up late. An object without the method fails at the call with TypeError: x.speak is not a function. Sharing a parent class keeps the family of objects consistent.
The exercise, step by step
The starter code defines Animal, Dog and Cat, each with a speak method. What is missing is the code that uses them, written below the classes at the top level of the file.
- Build the list. Declare
animals, an array holding anew Dog()and anew Cat(). Arrays can hold any mix of values, and both of these are animals. - Loop and call. Use
for...ofwith a variablea, and calla.speak()inside the loop. The loop has one call site. The dog printsWoofand the cat printsMeow, because each object's own class supplies the method.
The solution in JavaScript
class Animal {
speak() {
console.log("...");
}
}
class Dog extends Animal {
speak() {
console.log("Woof");
}
}
class Cat extends Animal {
speak() {
console.log("Meow");
}
}
let animals = [new Dog(), new Cat()];
for (let a of animals) {
a.speak();
}
The output is:
Woof
Meow
Common mistakes
- Checking the class by hand. Writing
if (a instanceof Dog) ... else ...inside the loop defeats the purpose. Call the method and let the object decide. - Forgetting
newin the array.[Dog, Cat]holds the classes themselves, anda.speak()then fails because the class has no such method, only its instances do. - Calling
speakwithout parentheses.a.speakevaluates to the function and doesn't run it. - Misspelling an override. If
CatdefinesSpeakwith a capital letter, the cat falls back toAnimal's..., with no error. - Putting the loop above the class definitions. Classes can't be used before their declarations run, so the array has to be created below them.
Key takeaways
- Polymorphism lets one call, such as
a.speak(), behave differently for different objects. - JavaScript picks the method from the object's actual class at the time of the call.
- New subclasses work with existing code without changing it.
- A subclass that doesn't override a method uses the parent's version.