Learn by reading through in order

Polymorphism — Same Call, Different Behavior

Give each class a method with the same name, and one call works for all of them. Covers mixed arrays, instanceof checks, and duck typing.

Imagine a contact form that lists its attachments, showing a thumbnail for images and a play button for videos. If you store the type as a string and branch with if in the display function, you have to open that function and add to it every time you add a type.

This article covers polymorphism, where a method with the same name runs different code in each class, and duck typing, which doesn't care about inheritance at all.

Putting Display Differences in Classes — Methods with the Same Name

Suppose each attachment is an object like { kind: "video", fileName: "demo.mp4" }, and a function called previewOf compares kind. To add videos, you open previewOf, which also holds the branch for images, and add to it.

With polymorphism (giving separate classes a method with the same name, so the caller writes the same call and the code of the class that created the instance runs), you put each type's display in the child class's preview.

Once each child class overrides the parent's preview, the caller just writes preview() without checking which child class it has.

// Store the type as a string and branch with if in the display function
function previewOf(file) {
  if (file.kind === "image") return `${file.fileName}: Show thumbnail`;
  return `${file.fileName}: No preview`;
}
console.log(previewOf({ kind: "video", fileName: "demo.mp4" }));   // demo.mp4: No preview

// Write a preview method with the same name in each type's class
class Attachment {
  constructor(fileName) { this.fileName = fileName; }
  preview() { return `${this.fileName}: No preview`; }
}
class ImageFile extends Attachment {
  preview() { return `${this.fileName}: Show thumbnail`; }
}
class VideoFile extends Attachment {
  preview() { return `${this.fileName}: Show play button`; }
}
console.log(new ImageFile("logo.png").preview());   // logo.png: Show thumbnail
console.log(new VideoFile("demo.mp4").preview());   // demo.mp4: Show play button
Code You Open to Add Video Previews
Add a videopreviewOpenpreviewOfEdit the functionthat has theimage branchImage previewsmay change tooAdd a videopreviewWriteclass VideoFileImageFileuntouchedImage previewsstay the same
The top row edits previewOf, the same function that holds the image branch. The bottom row only adds VideoFile and never touches ImageFile.

The first console.log printed "No preview" because previewOf doesn't have a "video" branch yet. With classes, whoever adds videos only needs to use the same name, preview, and doesn't have to remember which strings go in kind.

On a checkout screen, you'll show the amount due for each payment method, including its fee. PaymentMethod is already declared.

① Define a class for convenience store payment that inherits from PaymentMethod.

② In the same way, define a class for cash on delivery.

③ Create a credit card, a convenience store payment, and a cash on delivery method, and display the amount due for 4980 yen.

④ Display the amount due for 12800 yen with cash on delivery.

(If it runs correctly, an explanation will appear.)

JavaScript / TypeScript Editor

Run code to see output

Calling a Mix of Types at Once — Arrays and for...of

The list arrives as a single array that mixes images, PDFs, and files of unknown type. If you put each one in its own variable and write out a preview() call for each, you'll have to add or remove calls every time the number of attachments changes.

You can put instances of different classes into one array. When you loop over it with for...of, the file.preview() in the loop body runs on an instance of a different class on each pass, and the call stays a single line.

When you add PdfFile for PDFs, you don't touch the loop.

// Attachment and ImageFile are defined as in the first example
// A type added later. The loop doesn't change
class PdfFile extends Attachment {
  preview() { return `${this.fileName}: Show page 1`; }
}

const attachments = [
  new ImageFile("logo.png"),
  new PdfFile("manual.pdf"),
  new Attachment("memo.txt"),
];

// The body is one line. On each pass, file holds an instance of a different class
for (const file of attachments) {
  console.log(file.preview());
}
// logo.png: Show thumbnail
// manual.pdf: Show page 1
// memo.txt: No preview
Which preview Runs on Each Pass
for...of bodyfile.preview()Pass 1logo.pngPass 2manual.pdfPass 3memo.txtImageFile'spreviewPdfFile'spreviewAttachment'spreviewlogo.png:Show thumbnailmanual.pdf:Show page 1memo.txt:No preview
The call in the loop is a single line, but a different method body runs on each pass. Which preview runs depends on the class that created that pass's file.

memo.txt on the third pass was created from Attachment, so Attachment's own preview runs. If a new child class forgets to define preview, it falls back to this same output instead of throwing an error, so whenever you add a type, check its line in the list.

On an order confirmation screen, you'll suggest the payment method with the lowest amount due for each order total. PaymentMethod, BankTransfer, PayLater, and orderTotals are already declared.

① Define a class for installment payment that adds 3% of the price as a fee.

② Put the three payment methods into one array.

③ For each order total, pick the payment method with the lowest amount due.

④ Display the payment method you picked and its amount due.

JavaScript / TypeScript Editor

Run code to see output

Calling Code Only One Type Has — Branching with instanceof

Next, the image rows in the list need a crop button. cropButton, which returns the button, is defined only in ImageFile, so calling file.cropButton() on every item in the loop throws a TypeError on the memo.txt pass.

instanceof returns true if the value on the left was created from the class on the right or one of its child classes. Check file instanceof ImageFile and call cropButton only on the passes where it's true.

Since every class has preview, you can call it directly without a check.

class Attachment {
  constructor(fileName) { this.fileName = fileName; }
  preview() { return `${this.fileName}: No preview`; }
}
class ImageFile extends Attachment {
  preview() { return `${this.fileName}: Show thumbnail`; }
  cropButton() { return `${this.fileName}: Show crop button`; }   // Only ImageFile has this
}

const attachments = [new ImageFile("logo.png"), new Attachment("memo.txt")];

for (const file of attachments) {
  console.log(file.preview());          // Every class has it, so call it without a check
  if (file instanceof ImageFile) {
    console.log(file.cropButton());     // Call it only on ImageFile passes
  }
}
// logo.png: Show thumbnail
// logo.png: Show crop button
// memo.txt: No preview
Deciding Which Passes Call cropButton
file.cropButton()on every itemfile on pass 2is memo.txtAttachment hasno cropButtonThrows aTypeErrorBranch oninstanceofImageFilefalse on thememo.txt passMoves on withoutcalling cropButtonOnly logo.pnggets a button
The top row throws on the memo.txt pass, which has no cropButton. The bottom row calls it only on ImageFile passes.

The top row fails with TypeError: file.cropButton is not a function. If you branch on instanceof even for preview, you end up adding a check for every type, just like previewOf. Limit instanceof branches to methods that only some classes have.

Wrap instanceof in Parentheses When Using !

In !file instanceof ImageFile, !file is evaluated first and becomes false, so the check is really false instanceof ImageFile, which is always false. To say "not an ImageFile", wrap it in parentheses: !(file instanceof ImageFile).

For an order that can be paid with points, you'll add the remaining points to the points row only. PaymentMethod, PointPayment, methods, and cartTotal are already declared.

① Go through the payment methods in order, and display the amount due for each.

② On the points row only, append the points left after paying.

③ Display how many payment methods aren't points.

JavaScript / TypeScript Editor

Run code to see output

Listing Values That Don't Inherit — Duck Typing

The list also needs to show folder links. A link is an object literal with a preview() method, and it doesn't inherit from Attachment. If you check instanceof Attachment to filter out old data without preview, such as { fileName: "old.doc" }, the link also gets false and is lumped in with the old data as "Can't display".

With duck typing (deciding how to handle a value based only on whether it has the methods you need, without checking its class or inheritance), you check typeof item.preview === "function".

A method call works whatever the class is, as long as the value to the left of the dot has a function with that name.

class Attachment {
  constructor(fileName) { this.fileName = fileName; }
  preview() { return `${this.fileName}: No preview`; }
}

// A link made without a class. It doesn't inherit from Attachment
const folderLink = {
  fileName: "Quotes folder",
  preview() { return `${this.fileName}: Open link`; },
};
console.log(folderLink instanceof Attachment);   // false

// Decide by whether it has preview as a function, not by its class
for (const item of [new Attachment("memo.txt"), folderLink, { fileName: "old.doc" }]) {
  if (typeof item.preview === "function") {
    console.log(item.preview());                   // memo.txt: No preview / Quotes folder: Open link
  } else {
    console.log(`${item.fileName}: Can't display`);   // old.doc: Can't display
  }
}
How the List Handles folderLink
instanceofAttachmentNot createdfrom a classinstanceofis falseQuotes folder:Can't displaytypeofitem.previewHas a previewfunctiontypeof is"function"Quotes folder:Open link
The same folderLink is shown differently depending on how you check it. typeof ignores the class and only checks whether preview is a function.

Since there's no instanceof check, a value whose preview function doesn't return a string also gets through. Make sure every value you put in the list has a preview() that returns a display string. The table below compares the three ways of calling used in this article.

How You Call ItWhat It ChecksWhen to Use It
Call preview() directlyNothingEvery element has preview
Branch on instanceofCreated from ImageFile or a child?To call a method only that class has
Branch on typeofIs preview a function?To accept any value with preview

in Returns true Even If preview Isn't a Function

"preview" in item is true even when the value is a string, as in { preview: "Coming soon" }. Calling item.preview() on that value throws TypeError: item.preview is not a function, so before calling it, use typeof to check that it's actually a function.

QUIZ

Knowledge Check

Answer each question one by one.

Q1You're adding PdfFile to a list that has a loop calling file.preview(). What do you need to add?

Q2For memo, created from Attachment, what is the result of !memo instanceof ImageFile?

Q3For an object literal link with a preview() method, what are link instanceof Attachment and typeof link.preview?