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.