JavaScript OOP: classi, prototipi e gestione dei metodi condivisi

by Anastasia P.
SHARE
JavaScript OOP: classi, prototipi e gestione dei metodi condivisi
© Guida-HTML5.it

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() e markAsPaid().
  • Metodi aggiunti al prototipo, come getTotal() e getSummary().

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() e hasOwnProperty() 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.

SHARE