Nella programmazione orientata agli oggetti una classe è un costrutto di un linguaggio di programmazione usato come modello per creare oggetti. Il modello comprende attributi e metodi che saranno condivisi da tutti gli oggetti creati (istanze) a partire dalla classe. Un âoggettoâ è, di fatto, lâistanza di una classe.
In pratica, spesso abbiamo bisogno di creare più oggetti dello stesso tipo, come utenti, beni o altro.
Come già sappiamo dal capitolo Costruttore, operatore "new", new function ci può aiutare in questo.
Ma nel JavaScript moderno câè un costrutto âclassâ più avanzato, che introduce nuove possibilità molto utili per la programmazione ad oggetti.
La sintassi di âclassâ
La sintassi base è:
class MyClass {
// metodi della classe
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
new MyClass() creerà un nuovo oggetto con tutti i metodi presenti nella classe.
Il metodo constructor() viene chiamato automaticamente da new, dunque possiamo usarlo per inizializzare lâoggetto.
Per esempio:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// Utilizzo:
let user = new User("John");
user.sayHi();
Quando viene chiamato new User("John"):
- Viene creato un nuovo oggetto;
- Il metodo
constructor()viene richiamato e assegna athis.namelâargomento dato.
â¦Ora possiamo chiamare i metodi, per esempio user.sayHi.
Un errore comune per i principianti è separare i metodi con delle virgole, portando ad un syntax error.
La notazione delle classi non va confusa con la notazione letterale per gli oggetti. In una classe non sono richieste virgole.
Cosâè una classe?
Dunque, cosâè esattamente una class? A differenza di ciò che si potrebbe pensare, non si tratta di un concetto completamente nuovo.
Vediamo quindi cosâè effettivamente una classe. Questo ci aiuterà a comprendere aspetti più complessi.
In JavaScript, una classe è una specie di funzione.
Osserva:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// prova: User è una funzione
alert(typeof User); // function
Il costrutto class User {...} dunque:
- Crea una funzione chiamata
User, che diventa il risultato della dichiarazione della classe. Le istruzioni della funzione provengono dal metodoconstructor(considerato vuoto se non presente); - Salva tutti i metodi (come
sayHi) allâinterno diUser.prototype.
Quando richiameremo da un oggetto un metodo, questo verrà preso dal prototipo (prototype), come descritto nel capitolo F.prototype. Dunque un oggetto new User ha accesso ai metodi della classe.
Possiamo rappresentare il risultato della dichiarazione di class User come:
Il codice seguente ti permetterà di analizzarlo:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// una classe è una funzione
alert(typeof User); // function
// ...o, più precisamente, il costruttore
alert(User === User.prototype.constructor); // true
// I metodi sono in User.prototype:
alert(User.prototype.sayHi); // il codice del metodo sayHi
// ci sono due funzioni all'interno del prototipo
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
Non solo una semplificazione (syntax sugar)
Talvolta si pensa che class in JavaScript sia solo âsyntax sugarâ (una sintassi creata per semplificare la lettura, ma che non apporta nulla di nuovo), dato che potremmo potremmo dichiarare la stessa cosa senza utilizzare la parola chiave class:
// la classe User usando solo funzioni
// 1. Costruttore
function User(name) {
this.name = name;
}
// tutte le funzioni hanno un costruttore predefinito (di default)
// dunque non va creato
// 2. Aggiungiamo un metodo al prototipo
User.prototype.sayHi = function() {
alert(this.name);
};
// Utilizzo:
let user = new User("John");
user.sayHi();
Il risultato di questo codice è circa lo stesso. à quindi logico pensare che class sia solo una semplificazione sintattica (syntax sugar).
Ci sono però delle importanti differenze.
-
Una funzione creata attraverso
classviene etichettata dalla speciale proprietà interna[[IsClassConstructor]]: true. Quindi non è esattamente uguale che crearla manualmente.A differenza di una normale funzione, il costruttore di una classe può essere richiamato solo attraverso la parola chiave
new:class User { constructor() {} } alert(typeof User); // funzione User(); // Errore: Il costruttore della classe può essere richiamato solo attraverso 'new'Inoltre, nella maggior parte dei motori JavaScript il costruttore comincia con âclassâ
class User { constructor() {} } alert(User); // class User { ... }Ci sono altre differenze, che scopriremo più avanti.
-
I metodi delle classi non sono numerabili. La definizione di una classe imposta il flag
enumerableafalseper tutti i metodi allâinterno di"prototype".Questo è un bene, dato che non vogliamo visualizzare i metodi quando utilizziamo un ciclo
for..inper visualizzare un oggetto. -
Il contenuto di una classe viene sempre eseguito in
strict.Oltre a queste, la sintassi
classapporta altre caratteristiche, che esploreremo più avanti.
Lâespressione class
Come le funzioni, le classi possono essere definite allâinterno di unâaltra espressione, passata come parametro, essere ritornata (returned), assegnata (assigned) ecc.
Qui câè un piccolo esempio:
let User = class {
sayHi() {
alert("Hello");
}
};
In maniera simile alle funzione nominate (Named Function Expression), le classi possono avere o meno un nome.
Se una classe ha un nome, esso è visibile solo allâinterno della classe:
// "Named Class Expression"
// (la classe non ha un nome)
let User = class MyClass {
sayHi() {
alert(MyClass); // MyClass è visibile solo all'interno della classe
}
};
new User().sayHi(); // funziona, restituisce la definizione di MyClass
alert(MyClass); // errore, MyClass non è visibile al di fuori della classe
Possiamo anche creare delle classi âon-demandâ:
function makeClass(phrase) {
// dichiara una classe e la restituisce
return class {
sayHi() {
alert(phrase);
}
};
}
// Crea una nuova classe
let User = makeClass("Hello");
new User().sayHi(); // Hello
Getters/setters e altre scorciatoie
Così come negli oggetti letterali (literal objects), le classi possono includere getters/setters, generatori, proprietà eccetera.
Lâesempio seguente implementa user.name attraverso get/set:
class User {
constructor(name) {
// invoca il setter
this.name = name;
}
get name() {
return this._name;
}
set name(value) {
if (value.length < 4) {
alert("Name is too short.");
return;
}
this._name = value;
}
}
let user = new User("John");
alert(user.name); // John
user = new User(""); // Nome troppo corto.
La dichiarazione della classe crea i getter e i setter allâinterno di User.prototype:
Computed names [â¦]
A seguire un esempio con le proprietà :
function f() { return "sayHi"; }
class User {
[f()]() {
alert("Hello");
}
}
new User().sayHi();
Per creare un metodo generatore è sufficiente aggiungere * prima del nome della funzione.
Proprietà di una classe
Le proprietà di una classe dichiarata in questo modo sono una novità del linguaggio.
Negli esempi riportati sopra, la classe User conteneva solo dei metodi. Aggiungiamo una proprietà :
class User {
name = "Anonymous";
sayHi() {
alert(`Hello, ${this.name}!`);
}
}
new User().sayHi(); // Hello, John!
Quindi scriviamo semplicemente â
La differenza importante dei campi di una classe è che vengono impostati sullâoggetto individuale e non su User.prototype:
class User {
name = "John";
}
let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined
Possiamo anche assegnare valori utilizzando espressioni più complesse e chiamate a funzioni:
class User {
name = prompt("Name, please?", "John");
}
let user = new User();
alert(user.name); // John
Creazione di metodi vincolati a campi di classe
Come dimostrato nel capitolo Function binding, le funzioni in JavaScript hanno un this dinamico che dipende dal contesto della chiamata.
Quindi, se un metodo di un object viene passato e chiamato in un altro contesto, this non sarà più un riferimento al suo object.
Per esempio, questo codice mostrerà undefined:
class Button {
constructor(value) {
this.value = value;
}
click() {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // undefined
Il problema viene chiamato âperdita del thisâ.
Ci sono due differenti approcci per affrontare questo problema, come discusso nel capitolo Function binding:
- Passare una funzione contenitore, come
setTimeout(() => button.click(), 1000). - Associare il metodo allâoggetto, e.g. nel costruttore.
I campi di una classe forniscono unâaltra sintassi molto più elegante:
class Button {
constructor(value) {
this.value = value;
}
click = () => {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // hello
Il campo della classe click = () => {...} viene creato per ogni oggetto, abbiamo quindi una funzione diversa per ogni Button, con il riferimento this che punta allâoggetto. Possiamo passare button.click ovunque, e il valore di this sarà sempre quello corretto.
Questo è particolarmente utile in ambiente browser, per gli event listeners (ascoltatori di eventi).
Riepilogo
Il seguente esempio riporta la sintassi base di una classe:
class MyClass {
prop = value; // proprietÃ
constructor(...) { // costruttore
// ...
}
method(...) {} // metodo
get something(...) {} // metodo getter
set something(...) {} // metodo setter
[Symbol.iterator]() {} // metodo creato con un vettore relazionale
// ...
}
MyClass è tecnicamente una funzione (che corrisponde a constructor), mentre i metodi vengono scritti in MyClass.prototype.
Nei prossimi capitoli impareremo altri dettagli riguardo alle classi, come lâereditarietà .
Commenti
<code>, per molte righe â includile nel tag<pre>, per più di 10 righe â utilizza una sandbox (plnkr, jsbin, codepenâ¦)