Getters e Setters in Javascript
- Le proprietà di accesso (getter e setter) consentono di controllare l'accesso e la modifica delle proprietà degli oggetti in Javascript.
- I getter restituiscono il valore di una proprietà quando viene letta, mentre i setter impostano il valore di una proprietà quando viene scritta.
- Le proprietà di accesso possono essere definite utilizzando la sintassi degli oggetti con
get
eset
. - I getter e setter possono essere utilizzati per calcolare valori dinamicamente o per eseguire operazioni aggiuntive quando le proprietà vengono lette o scritte.
- Le proprietà di accesso sono ereditate, quindi possono essere utilizzate in oggetti che ereditano da altri oggetti.
Getter e Setter delle Proprietà di un Oggetto
Tutte le proprietà degli oggetti che abbiamo discusso finora nelle lezioni precedenti erano proprietà dati, chiamate anche campi, con un nome e un valore ordinario.
JavaScript supporta anche le proprietà di accesso, che non hanno un singolo valore ma invece hanno uno o due metodi di accesso:
-
Un metodo di accesso in lettura: getter
Restituisce il valore della proprietà quando viene letta.
-
Un metodo di accesso in scrittura: setter
Imposta il valore della proprietà quando viene scritta.
Quando un programma interroga il valore di una proprietà di accesso, JavaScript invoca il metodo getter (senza passare argomenti). Il valore di ritorno di questo metodo diventa il valore dell'espressione di accesso alla proprietà. Quando un programma imposta il valore di una proprietà di accesso, JavaScript invoca il metodo setter, passando il valore del lato destro dell'assegnazione. Questo metodo è responsabile di "impostare", in un certo senso, il valore della proprietà. Il valore di ritorno del metodo setter viene ignorato.
Se una proprietà ha sia un metodo getter che un setter, è una proprietà di lettura/scrittura. Se ha solo un metodo getter, è una proprietà di sola lettura. E se ha solo un metodo setter, è una proprietà di sola scrittura (qualcosa che non è possibile con le proprietà dati), e i tentativi di leggerla restituiscono sempre undefined
.
Le proprietà di accesso possono essere definite con un'estensione della sintassi letterale degli oggetti (a differenza delle altre estensioni ES6 che abbiamo visto, i getter e i setter sono stati introdotti in ES5):
let o = {
// Una proprietà dati ordinaria
proprietaDati: valore,
// Una proprietà di accesso definita come una coppia di funzioni.
get proprietaAccesso() {
return this.proprietaDati;
},
set proprietaAccesso(valore) {
this.proprietaDati = valore;
}
};
Le proprietà di accesso sono definite come uno o due metodi il cui nome è lo stesso del nome della proprietà. Questi sembrano metodi ordinari definiti usando la sintassi abbreviata ES6 eccetto che le definizioni getter e setter sono precedute da get
o set
. (In ES6, è possibile usare anche nomi di proprietà calcolati quando si definiscono getter e setter. Semplicemente basta sostituire il nome della proprietà dopo get
o set
con un'espressione tra parentesi quadre.)
I metodi di accesso definiti sopra semplicemente ottengono e impostano il valore di una proprietà dati, e non c'è ragione di preferire la proprietà di accesso rispetto alla proprietà dati.
Ma come esempio più interessante, consideriamo il seguente oggetto che rappresenta un punto cartesiano 2D. Ha proprietà dati ordinarie per rappresentare le coordinate x
e y
del punto, e ha proprietà di accesso che forniscono le coordinate polari equivalenti del punto:
let p = {
// x e y sono proprietà dati regolari di lettura-scrittura.
x: 1.0,
y: 1.0,
// r è una proprietà di accesso di lettura-scrittura con getter e setter.
// Non bisogna dimenticare di mettere una virgola dopo i metodi di accesso.
get r() {
// Restituisce la distanza dal punto all'origine
return Math.hypot(this.x, this.y);
},
set r(nuovoValore) {
let vecchioValore = Math.hypot(this.x, this.y);
let rapporto = nuovoValore / vecchioValore;
this.x *= rapporto;
this.y *= rapporto;
},
// theta è una proprietà di accesso di sola lettura con solo getter.
get theta() {
return Math.atan2(this.y, this.x);
}
};
console.log(p.r); // => 1.4142135623730951: distanza dal punto all'origine
console.log(p.theta); // => 0.7853981633974483: angolo rispetto all'asse x
Si noti l'uso della parola chiave this
nei getter e setter in questo esempio. JavaScript invoca queste funzioni come metodi dell'oggetto su cui sono definiti, il che significa che all'interno del corpo della funzione, this
si riferisce all'oggetto punto p
. Quindi il metodo getter per la proprietà r
può riferirsi alle proprietà x
e y
come this.x
e this.y
. I metodi e la parola chiave this
sono coperti in maggior dettaglio nelle lezioni sui metodi.
Le proprietà di accesso sono ereditate, proprio come le proprietà dati, quindi si può usare l'oggetto p
definito sopra come prototipo per altri punti. Si può dare ai nuovi oggetti le loro proprietà x
e y
, e essi erediteranno le proprietà r
e theta
:
let q = Object.create(p); // Un nuovo oggetto che eredita getter e setter
q.x = 3; q.y = 4; // Crea le proprietà dati proprie di q
q.r // => 5: le proprietà di accesso ereditate funzionano
q.theta // => Math.atan2(4, 3)
Il codice sopra usa le proprietà di accesso per definire un'API che fornisce due rappresentazioni (coordinate cartesiane e coordinate polari) di un singolo set di dati. Altre ragioni per usare le proprietà di accesso includono il controllo di validità delle scritture di proprietà e la restituzione di valori diversi ad ogni lettura di proprietà:
// Questo oggetto genera numeri seriali strettamente crescenti
const numerazioneSerie = {
// Questa proprietà dati contiene il prossimo numero seriale.
// Il _ nel nome della proprietà suggerisce che è solo per uso interno.
_n: 0,
// Restituisce il valore corrente e lo incrementa
get successivo() { return this._n++; },
// Imposta un nuovo valore di n, ma solo se è più grande del corrente
set successivo(n) {
if (n > this._n)
this._n = n;
else
throw
new Error(
"il numero seriale può essere impostato solo a un valore più grande");
}
};
numerazioneSerie.successivo = 10; // Imposta il numero seriale di partenza
numerazioneSerie.successivo // => 10
numerazioneSerie.successivo // => 11: valore diverso ogni volta che otteniamo successivo
Infine, ecco un altro esempio che usa un metodo getter per implementare una proprietà con comportamento "magico":
// Questo oggetto ha proprietà di accesso che restituiscono numeri casuali.
// L'espressione "casuale.ottetto", per esempio, produce un numero casuale
// tra 0 e 255 ogni volta che viene valutata.
const casuale = {
get ottetto() { return Math.floor(Math.random()*256); },
get uint16() { return Math.floor(Math.random()*65536); },
get int16() { return Math.floor(Math.random()*65536)-32768; }
};
Questo oggetto ha tre proprietà di accesso che restituiscono numeri casuali ogni volta che vengono lette. Si noti che non c'è un metodo setter per queste proprietà, quindi sono di sola lettura.