Le class in JavaScript sono zucchero sintattico: sotto, l'ereditarietà è basata su prototipi. Ogni oggetto ha un collegamento a un altro oggetto (il suo prototipo) da cui eredita metodi.

const animale = {
  parla() { return "..."; },
};

const cane = Object.create(animale);
cane.parla = function () { return "Bau"; };

cane.parla();          // "Bau" (proprio)
cane.hasOwnProperty("parla"); // true
Object.getPrototypeOf(cane) === animale; // true

Quando chiami un metodo, JavaScript lo cerca sull'oggetto; se non c'è, risale la catena dei prototipi finché lo trova (o arriva a null). È così che [].map funziona: map vive su Array.prototype.

Capire la catena spiega tanti misteri: perché class extends funziona, perché aggiungere a Array.prototype è pericoloso, e perché Object.create è ereditarietà allo stato puro.