Getters e setters sono funzioni o metodi usati per ottenere e impostare i valori delle variabili. Il concetto di getter-setter è comune nella programmazione dei computer: quasi tutti i linguaggi di programmazione ad alto livello hanno una serie di sintassi per implementare getter e setter, incluso JavaScipt.
Leggi anche: 4 utili dichiarazioni JavaScript che dovresti conoscere
In questo post, vedremo cosa sono i getter setter e come crearli e usarli in JavaScript.
- Getters-setters e incapsulamento
- Creare getter e setter
- 1. Con i metodi
- 2. Con le parole chiave
- Come è meglio?
- Prevenzione della sovrascrittura
- Operazioni all’interno di getter e setter
- Proteggere i dati con getter e setter
- Dati non protetti
- 1. Scoping a blocchi
- 2. Scoping di funzione
- 3. Protezione dei dati senza scoping
- Quando dovreste usare getter e setter?
Getters-setters e incapsulamento
L’idea di getter e setter è sempre menzionata insieme all’incapsulamento. L’incapsulamento può essere inteso in due modi.
In primo luogo, è l’impostazione del trio dati-getters-setters per accedere e modificare quei dati. Questa definizione è utile quando alcune operazioni, come la convalida, devono essere eseguite sui dati prima di salvarli o visualizzarli – i getter e i setter forniscono la casa perfetta per questo.
In secondo luogo, c’è una definizione più rigida secondo la quale l’incapsulamento è fatto per nascondere i dati, per renderli inaccessibili da altro codice, tranne che attraverso i getter e i setter. In questo modo non si finisce per sovrascrivere accidentalmente dati importanti con qualche altro codice nel programma.
Creare getter e setter
1. Con i metodi
Siccome i getter e i setter sono fondamentalmente funzioni che recuperano/cambiano un valore, ci sono più modi per crearli e usarli. Il primo modo è:
var obj = { foo: 'this is the value of foo', getFoo: function() { return this.foo; }, setFoo: function(val) { this.foo = val; }}console.log(obj.getFoo());// "this is the value of foo"obj.setFoo('hello');console.log(obj.getFoo());// "hello"
Questo è il modo più semplice per creare getter e setter. C’è una proprietà foo
e ci sono due metodi: getFoo
e setFoo
per restituire e assegnare un valore a quella proprietà.
2. Con le parole chiave
Un modo più “ufficiale” e robusto di creare getter e setter è usando le parole chiave get
e set
.
Per creare un getter, mettere la parola chiave get
davanti alla dichiarazione di una funzione che servirà come metodo getter, e usare la parola chiave set
allo stesso modo per creare un setter. La sintassi è la seguente:
var obj = { fooVal: 'this is the value of foo', get foo() { return this.fooVal; }, set foo(val) { this.fooVal = val; }}console.log(obj.foo);// "this is the value of foo"obj.foo = 'hello';console.log(obj.foo);// "hello"
Nota che i dati possono essere memorizzati solo sotto un nome di proprietà (fooVal
) diverso dal nome dei metodi getter-setter (foo
) perché una proprietà che contiene il getter-setter non può contenere anche i dati.
Come è meglio?
Se scegliete di creare getter e setter con parole chiave, potete usare l’operatore di assegnazione per impostare i dati e l’operatore di punto per ottenere i dati, nello stesso modo in cui accedereste/impostereste il valore di una normale proprietà.
Tuttavia, se scegliete il primo modo di codificare i getter e i setter, dovete chiamare i metodi setter e getter usando la sintassi di chiamata di funzione perché sono funzioni tipiche (niente di speciale come quelle create usando le parole chiave get
e set
).
Inoltre, c’è la possibilità che finiate per assegnare accidentalmente qualche altro valore alle proprietà che contengono quei metodi getter-setter e li perdiate completamente! Qualcosa di cui non dovete preoccuparvi nel metodo successivo.
Così, potete vedere perché ho detto che la seconda tecnica è più robusta.
Prevenzione della sovrascrittura
Se per qualche ragione preferite la prima tecnica, rendete le proprietà che tengono i metodi getter-setter di sola lettura creandole con Object.defineProperties
. Le proprietà create tramite Object.defineProperties
, Object.defineProperty
e Reflect.defineProperty
si configurano automaticamente a writable: false
che significa sola lettura:
/* Overwrite prevention */var obj = { foo: 'this is the value of foo'};Object.defineProperties(obj, { 'getFoo': { value: function () { return this.foo; } }, 'setFoo': { value: function (val) { this.foo = val; } }});obj.getFoo = 66;// getFoo is not going to be overwritten!console.log(obj.getFoo());// "this is the value of foo"
Operazioni all’interno di getter e setter
Una volta introdotti i getter e i setter, potete procedere ed eseguire operazioni sui dati prima di modificarli o restituirli.
Nel codice qui sotto, nella funzione getter i dati vengono concatenati con una stringa prima di essere restituiti, e nella funzione setter viene eseguita una validazione del fatto che il valore sia un numero o meno prima di aggiornare n
.
var obj = { n: 67, get id() { return 'The ID is: ' + this.n; }, set id(val) { if (typeof val === 'number') this.n = val; }}console.log(obj.id);// "The ID is: 67"obj.id = 893;console.log(obj.id);// "The ID is: 893"obj.id = 'hello';console.log(obj.id);// "The ID is: 893"
Proteggere i dati con getter e setter
Finora abbiamo coperto l’uso di getter e setter nel primo contesto di incapsulamento. Passiamo al secondo, cioè come nascondere i dati dal codice esterno con l’aiuto di getter e setter.
Dati non protetti
L’impostazione di getter e setter non significa che i dati siano accessibili e modificabili solo attraverso quei metodi. Nell’esempio seguente, vengono cambiati direttamente senza toccare i metodi getter e setter:
var obj = { fooVal: 'this is the value of foo', get foo() { return this.fooVal; }, set foo(val) { this.fooVal = val; }}obj.fooVal = 'hello';console.log(obj.foo);// "hello"
Non abbiamo usato il setter ma abbiamo cambiato direttamente i dati (fooVal
). I dati che avevamo inizialmente impostato dentro obj
ora non ci sono più! Per evitare che questo accada (accidentalmente), avete bisogno di qualche protezione per i vostri dati. Potete aggiungerla limitando l’ambito in cui i vostri dati sono disponibili. Potete farlo sia con lo scoping a blocchi che con lo scoping a funzioni.
1. Scoping a blocchi
Un modo è quello di usare uno scope di blocco all’interno del quale i dati saranno definiti usando la parola chiave let
che limita il suo ambito a quel blocco.
Uno scope di blocco può essere creato mettendo il vostro codice dentro un paio di parentesi graffe. Ogni volta che create uno scope di blocco assicuratevi di lasciare un commento sopra di esso chiedendo che le parentesi graffe siano lasciate in pace, in modo che nessuno rimuova le parentesi per errore pensando che siano delle parentesi aggiuntive ridondanti nel codice o aggiunga un’etichetta allo scope di blocco.
/* BLOCK SCOPE, leave the braces alone! */{let fooVal = 'this is the value of foo';var obj = { get foo() { return fooVal; }, set foo(val) { fooVal = val } }}fooVal = 'hello';// not going to affect the fooVal inside the blockconsole.log(obj.foo);// "this is the value of foo"
Cambiare/creare fooVal
fuori dal blocco non influenzerà il fooVal
riferito dentro i getter setter.
2. Scoping di funzione
Il modo più comune per proteggere i dati con lo scoping è mantenere i dati all’interno di una funzione e restituire un oggetto con i getter e i setter di quella funzione.
function myobj(){ var fooVal = 'this is the value of foo'; return { get foo() { return fooVal; }, set foo(val) { fooVal = val } }}fooVal = 'hello';// not going to affect our original fooValvar obj = myobj();console.log(obj.foo);// "this is the value of foo"
L’oggetto (con il getter-setter foo()
al suo interno) restituito dalla funzione myobj()
viene salvato in obj
, e poi obj
viene usato per chiamare il getter e il setter.
3. Protezione dei dati senza scoping
C’è anche un altro modo per proteggere i dati dalla sovrascrittura senza limitarne lo scopo. La logica che sta dietro va così: come potete cambiare un pezzo di dati se non sapete come si chiama?
Se i dati hanno un nome di variabile/proprietà non facilmente riproducibile, è probabile che nessuno (nemmeno noi stessi) finisca per sovrascriverli assegnando un qualche valore a quel nome di variabile/proprietà.
var obj = { s89274934764: 'this is the value of foo', get foo() { return this.s89274934764; }, set foo(val) { this.s89274934764 = val; }}console.log(obj.foo);// "this is the value of foo"
Vedi, questo è un modo di risolvere le cose. Anche se il nome che ho scelto non è molto buono, potete anche usare valori casuali o simboli per creare nomi di proprietà come proposto da Derick Bailey in questo post del blog. L’obiettivo principale è quello di mantenere i dati nascosti da altro codice e lasciare che una coppia getter-setter vi acceda/aggiorni.
Quando dovreste usare getter e setter?
Ora arriva la grande domanda: iniziate ad assegnare getter e setter a tutti i vostri dati ora?
Se state nascondendo dei dati, allora non c’è altra scelta.
Ma se i vostri dati sono visti da altro codice, avete ancora bisogno di usare getter e setter solo per unirli al codice che esegue alcune operazioni su di essi? Direi di sì. Il codice si aggiunge molto presto. Creare micro unità di dati individuali con i propri getter-setter vi fornisce una certa indipendenza per lavorare su tali dati senza influenzare altre parti del codice.
Leggi anche: 10 motivi per cui avete bisogno dell’ottimizzazione del codice