Introduzione
Quando si parla di programmazione orientata agli oggetti in JavaScript, spesso ci si concentra sulle classi introdotte nella sintassi moderna. Tuttavia, sotto il cofano, JavaScript continua a basarsi sui prototipi. Capire bene questo meccanismo è fondamentale per scrivere codice più pulito, efficiente e facile da mantenere.
In questo tutorial vedremo un sotto-argomento molto pratico: come definire metodi condivisi in modo corretto usando classi e prototipi. È un tema utile perché aiuta a evitare duplicazioni inutili, a ridurre il consumo di memoria e a organizzare meglio il codice quando costruiamo oggetti con comportamento comune.
Vedremo un esempio realistico: una piccola gestione di ordini in un e-commerce. Useremo una classe per rappresentare un ordine, ma sposteremo alcuni metodi nel prototipo per riutilizzarli in modo efficiente. Questo approccio è molto utile quando vuoi creare istanze numerose con comportamenti simili.
Codice completo
class Order {
constructor(id, items, customer) {
this.id = id;
this.items = items;
this.customer = customer;
this.status = "pending";
}
// Metodo di istanza: ogni ordine può usarlo
addItem(item) {
this.items.push(item);
}
// Metodo di istanza: cambia lo stato dell´ordine
markAsPaid() {
this.status = "paid";
}
}
// Metodo condiviso aggiunto al prototipo dopo la definizione della classe
Order.prototype.getTotal = function () {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
};
// Altro metodo condiviso sul prototipo
Order.prototype.getSummary = function () {
return {
id: this.id,
customer: this.customer,
status: this.status,
total: this.getTotal()
};
};
// Dati di esempio
const order1 = new Order(
101,
[
{ name: "Keyboard", price: 50, quantity: 1 },
{ name: "Mouse", price: 25, quantity: 2 }
],
{ name: "Luca", email: "[email protected]" }
);
const order2 = new Order(
102,
[
{ name: "Monitor", price: 200, quantity: 1 },
{ name: "USB-C Cable", price: 15, quantity: 3 }
],
{ name: "Giulia", email: "[email protected]" }
);
// Uso dei metodi
order1.addItem({ name: "Headset", price: 80, quantity: 1 });
order1.markAsPaid();
console.log(order1.getSummary());
console.log(order2.getSummary());
// Verifica del prototipo
console.log(Object.getPrototypeOf(order1) === Order.prototype); // true
console.log(order1.hasOwnProperty("getTotal")); // false
console.log(order1.hasOwnProperty("addItem")); // true Spiegazione
La classe Order rappresenta un ordine di acquisto. Il costruttore inizializza i dati principali: identificativo, lista degli articoli, cliente e stato iniziale.
Nel codice abbiamo due tipi di metodi:
- Metodi definiti dentro la classe, come
addItem()emarkAsPaid(). - Metodi aggiunti al prototipo, come
getTotal()egetSummary().
In JavaScript, quando crei una classe, i metodi dichiarati nel corpo della classe vengono comunque memorizzati nel prototipo della classe. Questo significa che tutte le istanze condividono la stessa funzione. È molto importante perché evita di creare una copia del metodo per ogni oggetto.
Nel nostro esempio, getTotal() calcola il totale dell’ordine usando reduce(), mentre getSummary() restituisce un oggetto riassuntivo con i dati principali. Questi metodi sono perfetti per stare sul prototipo, perché descrivono un comportamento uguale per tutti gli ordini.
La riga Object.getPrototypeOf(order1) === Order.prototype conferma che l’istanza order1 punta al prototipo della classe. Inoltre, order1.hasOwnProperty("getTotal") restituisce false, perché getTotal non è una proprietà diretta dell’istanza, ma vive nel prototipo. Al contrario, addItem risulta come proprietà diretta solo se definita come funzione assegnata nell’istanza; nel nostro esempio, essendo scritta nel corpo della classe, in realtà è anch’essa nel prototipo. Se vuoi verificarlo correttamente, puoi controllare Order.prototype.hasOwnProperty("addItem").
Questo ci porta a un punto importante: la sintassi delle classi in JavaScript è più elegante, ma non elimina i prototipi. Li usa in modo più leggibile. Per questo è utile conoscere entrambi gli approcci.
Un altro aspetto pratico è la differenza tra metodi condivisi e funzioni definite nel costruttore. Se scrivi una funzione dentro il costruttore, ogni istanza riceve una nuova copia. Questo è quasi sempre inutile, a meno che il metodo debba catturare uno stato locale molto specifico. In generale, i metodi condivisi sono la scelta migliore.
Best practice
- Preferisci metodi condivisi per tutte le operazioni che non dipendono da variabili locali del costruttore.
- Evita di definire funzioni nel costruttore se devono essere identiche per tutte le istanze.
- Usa la sintassi class per migliorare la leggibilità, ma ricorda che il prototipo resta il meccanismo base.
- Controlla il prototipo quando debuggi:
Object.getPrototypeOf()ehasOwnProperty()sono strumenti molto utili. - Separa dati e comportamenti: i dati cambiano da istanza a istanza, i comportamenti comuni dovrebbero stare nel prototipo.
- Non abusare di metodi dinamici aggiunti in runtime se non hai una buona ragione architetturale.
Una buona regola pratica è questa: se un metodo farà la stessa cosa per tutti gli oggetti della stessa “famiglia”, mettilo nel prototipo. Se invece il comportamento è davvero specifico per ogni istanza, allora valuta con attenzione se deve stare nel costruttore.
Riepilogo
In JavaScript, classi e prototipi non sono mondi separati: le classi sono una sintassi più moderna sopra un sistema basato sui prototipi. Per scrivere codice orientato agli oggetti in modo efficace, è utile capire dove vivono davvero i metodi e come vengono condivisi tra le istanze.
In questo tutorial abbiamo visto come:
- creare una classe con un costruttore;
- definire metodi di istanza;
- aggiungere metodi condivisi al prototipo;
- verificare la relazione tra istanze e prototipo;
- usare i prototipi per scrivere codice più efficiente e manutenibile.
Se lavori su applicazioni reali, questa conoscenza ti aiuterà a progettare oggetti più puliti e a evitare duplicazioni inutili. È un passo importante per passare da un uso “superficiale” delle classi a una comprensione solida del modello OOP di JavaScript.
Approfondisci con risorse ufficiali
- MDN Web Docs - Classes: documentazione ufficiale e molto chiara sulle classi in JavaScript.
- MDN Web Docs - Inheritance and the prototype chain: ottima risorsa per capire il legame tra classi, oggetti e prototipi.
- MDN Web Docs - Object.prototype: utile per esplorare i metodi disponibili sugli oggetti.
- ECMAScript Language Specification: la specifica ufficiale per chi vuole approfondire il comportamento del linguaggio.
Se vuoi fare un passo in più, prova a riscrivere l’esempio aggiungendo metodi come applyDiscount() o removeItem(), e valuta quali di essi dovrebbero stare nel prototipo e quali nell’istanza. È un ottimo esercizio per consolidare il modello mentale di JavaScript OOP.
