Una classe è il progetto di un oggetto
Una classe descrive la forma e il comportamento di un tipo di oggetto. Definisci il progetto una volta, poi crei tutte le istanze che ti servono. Ogni istanza ha i propri dati ma condivide gli stessi metodi.
La forma di base:
class User { ... } definisce il progetto. new User(...) crea un'istanza. Ogni istanza riceve i propri name ed email, ma entrambe condividono l'unico metodo greet definito sulla classe.
Due istanze, due insiemi di dati, un solo insieme di metodi. Tutta l'idea è qui.
Il constructor prepara ogni istanza
Il constructor è un metodo speciale che viene eseguito esattamente una volta per istanza, nel momento in cui new la crea. Il suo compito è inizializzare il nuovo oggetto, di solito copiando gli argomenti su this:
Dentro il constructor, this si riferisce all'istanza nuova di zecca che si sta costruendo. Assegnare this.x = x mette x su quell'oggetto specifico. Ogni chiamata new Point(...) riceve il proprio this, e quindi i propri x e y.
Se non scrivi un constructor, JavaScript te ne dà uno vuoto gratis. Devi scriverlo solo quando hai davvero qualcosa da inizializzare.
È new a far funzionare il meccanismo
Chiamare una classe senza new genera un errore:
const p = Point(3, 4);
// TypeError: Class constructor Point cannot be invoked without 'new'
È voluto. new fa quattro cose, in quest'ordine:
- Crea un nuovo oggetto vuoto.
- Lo collega al prototype della classe (dove vivono i metodi).
- Esegue il
constructorconthislegato al nuovo oggetto. - Restituisce il nuovo oggetto.
Senza new, niente di tutto questo accade. La classe impone la regola così non puoi dimenticartelo per sbaglio: un vero miglioramento rispetto ai tempi prima di class, quando dimenticare new corrompeva in silenzio l'oggetto globale.
I metodi sono condivisi, i campi sono per istanza
I metodi definiti nel corpo della classe vivono sul prototype: una sola copia condivisa da tutte le istanze. I campi di istanza (tutto ciò che assegni a this) sono invece per istanza, copie separate ogni volta.
Entrambe le istanze hanno il proprio count, quindi incrementarne una non influisce sull'altra. Ma a.increment e b.increment sono letteralmente la stessa funzione: salvata una volta sul prototype e cercata ogni volta che la chiami su un'istanza. Ecco perché le classi scalano a basso costo: mille istanze non creano mille copie di ogni metodo.
Campi di classe
Puoi dichiarare i campi di istanza in cima al corpo della classe, fuori dal constructor:
Le dichiarazioni dei campi vengono eseguite prima del corpo del constructor, come se fossero scritte all'inizio di constructor. Sono utili quando il valore iniziale non dipende dagli argomenti del constructor: è più pulito che riempire il constructor con this.count = 0; this.step = 1;.
Quando i valori dipendono dagli argomenti, lascia l'assegnazione nel constructor, dove gli argomenti sono nello scope.
Getter e setter
Un getter o un setter sembra un metodo ma si comporta come l'accesso a una proprietà. Leggi o assegni senza parentesi, e sotto viene eseguito il metodo:
Guarda come vengono usati: t.fahrenheit (senza parentesi) legge il getter, t.fahrenheit = 100 attiva il setter. Dall'esterno, fahrenheit sembra una proprietà normale, ma viene calcolata al volo a partire da celsius.
I getter sono ottimi per i valori derivati. I setter sono ottimi per validare o normalizzare un valore quando viene assegnato. Non abusarne: se un getter è costoso, chi legge il codice resterà sorpreso che un "accesso a una proprietà" faccia un lavoro vero.
Sintassi abbreviata dei metodi, nomi calcolati e this
Le definizioni dei metodi in una classe usano la sintassi abbreviata: niente parola chiave function, niente : tra nome e corpo:
Restituire this da add permette di concatenare i metodi: ogni chiamata restituisce la stessa istanza, così il metodo successivo può essere eseguito su di essa.
Un tranello: i metodi non sono legati automaticamente alla loro istanza. Se estrai un metodo e lo chiami da solo, this si perde:
Chiamare g.hello() funziona perché il . fornisce this. Chiamare fn() da solo no. Se ti serve un metodo staccato e già legato (un caso comune negli event handler), fai il bind nel constructor oppure usa un campo di classe con una arrow function: hello = () => \Hi, ${this.name}`;`.
Un piccolo esempio funzionante
Mettiamo insieme i pezzi: campi, constructor, metodi e un getter:
Si legge a colpo d'occhio: la classe descrive esattamente cos'è un BankAccount e cosa può fare.
In realtà le classi sono funzioni
Una cosa da assimilare: class è soprattutto zucchero sintattico. Sotto il cofano, una classe è una funzione (per la precisione una funzione costruttore) e i suoi metodi vivono su ClassName.prototype:
typeof User è "function". greet vive su User.prototype. Le istanze trovano greet risalendo la catena dei prototype, lo stesso meccanismo che esiste nel linguaggio fin dal primo giorno. Le classi ti danno solo una sintassi pulita per configurarlo.
Questo modello mentale ripaga più avanti: ereditarietà, instanceof e il debug delle catene di prototype diventano molto più chiari quando ricordi che una classe è una funzione con i metodi appesi al suo prototype.
Prossimo passo: ereditarietà
Finora ogni classe è rimasta isolata. La maggior parte dei progetti reali costruisce gerarchie di classi: un Dog che è un tipo di Animal, un AdminUser che è un tipo di User. Se ne occupano le parole chiave extends e super, e sono l'argomento della prossima pagina.
Domande frequenti
Come si crea una classe in JavaScript?
Usa la parola chiave class seguita da un nome, poi un blocco con un metodo constructor e gli altri metodi che ti servono. Crei un'istanza con new ClassName(...). Esempio: class User { constructor(name) { this.name = name; } } e poi new User('Ada').
Cosa fa il constructor in una classe JavaScript?
Il constructor viene eseguito una sola volta, quando chiami new ClassName(...). Il suo compito è preparare la nuova istanza, di solito assegnando gli argomenti a this come proprietà dell'istanza. Se non ne scrivi uno, JavaScript ti fornisce automaticamente un constructor predefinito vuoto.
Che differenza c'è tra una classe e una funzione in JavaScript?
Sotto il cofano, una classe è una funzione: nello specifico una funzione costruttore con i suoi metodi agganciati al prototype. La parola chiave class è soprattutto zucchero sintattico, ma obbliga a chiamarla con new, supporta extends in modo pulito e rende i metodi non enumerabili. Per il codice nuovo, preferisci class alle funzioni costruttore scritte a mano.