- Learn
- Guida JavaScript
- La classe Proxy
La classe Proxy
La classe Proxy, introdotta con le specifiche ECMAScript 2015, consente di creare oggetti che hanno la capacità di modificare il comportamento predefinito di altri oggetti. Nella definizione di un proxy per un oggetto, cioè di un’istanza della classe Proxy, possiamo definire un handler e configurare trap per intercettare l’accesso alle sue proprietà ed eventualmente modificare il comportamento predefinito.
Per comprendere i concetti di base di un proxy, proviamo a fare un semplice esempio. Supponiamo di voler tracciare sulla console ogni accesso alle proprietà di un oggetto. Possiamo definire il seguente handler:
var handler = {
get(target, propertyName) {
console.log("Lettura di " + propertyName);
return target[propertyName];
},
set(target, propertyName, value) {
console.log("Assegnamento di " + value + " a " + propertyName);
target[propertyName] = value;
}
};
Questo handler non è altro che un oggetto con due metodi, get()
e set()
, che intercettano rispettivamente gli accessi in lettura e scrittura alle proprietà dell’oggetto che vogliamo
monitorare. I metodi dell’handler sono chiamati trap e
consentono di intercettare accessi e manipolazioni relative all’oggetto di
destinazione, il target.
Nello specifico, il metodo get()
scrive sulla console e
restituisce il valore della proprietà del target, mentre il metodo set()
scrive il valore sulla console ed assegna il valore del
parametro value alla proprietà del target. In questo caso vogliamo
mantenere il comportamento standard dell’oggetto target, ma in
generale possiamo restituire o assegnare alla proprietà del target
qualsiasi valore, modificando quindi il comportamento predefinito.
Una volta definito l’handler, possiamo creare un proxy per un oggetto specificandolo nel costruttore della classe Proxy, come nel seguente esempio:
var persona = {nome: "Mario", cognome: "Rossi"};
var personaProxata = new Proxy(persona, handler);
Abbiamo creato un oggetto persona e lo abbiamo passato insieme all’handler al costruttore della classe Proxy. D’ora in poi ogni accesso alle proprietà dell’oggetto personaProxata avrà effetto sull’oggetto persona e verrà intercettato e loggato sulla console:
var nome = personaProxata.nome;
//console: Lettura di nome
personaProxata.nome = "Marco";
//console: Assegnamento di Marco a nome
console.log(persona.nome);
//console: Marco
Naturalmente
questo è un semplice esempio per introdurre i concetti di base
per l’utilizzo della classe Proxy. È possibile utilizzare
altre trap per definire manipolazioni avanzate dell’oggetto
target. Oltre a get()
e set()
, infatti, possiamo
sfruttare le seguenti trap che intercettano i corrispondenti
metodi dell’oggetto target:
-
getPrototypeOf()
-
setPrototypeOf()
-
isExtensible()
-
preventExtensions()
-
getOwnPropertyDescriptor()
-
defineProperty()
-
has()
-
deleteProperty()
-
ownKeys()
-
apply()
-
construct()
Validazione con i proxy
Abbiamo visto i meccanismi di base dell’utilizzo della classe Proxy con un esempio molto semplice e didattico. Nella pratica i contesti di applicazione sono diversi e possono contribuire a scrivere codice meno invasivo e con una elevata separazione delle responsabilità. Proviamo a dare qualche indicazione fornendo qualche esempio più pratico.
Supponiamo di voler gestire la validazione dell’assegnamento di valori ad un oggetto senza modificare però l’oggetto in questione. Ad esempio, vogliamo che non sia possibile assegnare una stringa vuota o una stringa che contenga dei numeri al nome e cognome di un oggetto che rappresenti una persona. Possiamo allora definire un handler come il seguente:
var validatore = {
set(target, propertyName, value) {
if (propertyName == "nome" || propertyName == "cognome") {
if (value == "") throw new Error("Non è possibile assegnare una stringa vuota");
let hasNumberRegExp = /\d/;
if (hasNumberRegExp.test(value)) throw new Error("La stringa non può contenere numeri");
}
target[propertyName] = value;
}
};
Come possiamo vedere, abbiamo definito il metodo set()
all’interno
del quale abbiamo intercettato gli accessi alle proprietà nome e cognome. Nel caso in cui il valore che si tenta di assegnare a
queste proprietà è una stringa vuota o contiene un valore numerico, generiamo
un’eccezione con uno specifico messaggio. Se non si intercetta una
situazione non valida, viene effettuato l’assegnamento sulla proprietà
dell’oggetto target.
Vediamo come implementare e testare il proxy:
var personaConValidazione = new Proxy({nome: "Mario", cognome: "Rossi"}, validatore);
personaConValidazione.nome = "";
//Uncaught Error: Non è possibile assegnare una stringa vuota
personaConValidazione.nome = "Mario1"
//Uncaught Error: La stringa non può contenere numeri
In questo modo abbiamo isolato la logica di validazione in un componente esterno all’oggetto originario migliorando la manutenibilità del codice. Ad esempio, se i criteri di validazione per un oggetto cambiano da un contesto all’altro, non abbiamo bisogno di scrivere oggetti diversi o setter complessi per applicare il criterio corretto. È sufficiente scrivere handler diversi in base al contesto ed utilizzarli per creare il proxy opportuno.
Data binding
Un altro ambito in cui possiamo utilizzare la classe Proxy è nell’implementazione del data binding, cioè nel meccanismo che lega le proprietà di due oggetti in modo che le modifiche si propaghino da uno all’altro. Nel contesto del data binding si parla di un oggetto che fornisce dati (data source object) e di un oggetto che li riceve (data target object). L’esempio tipico di applicazione del data binding è quello che associa una proprietà di un oggetto con un elemento dell’interfaccia grafica, come ad esempio una casella di testo. Vediamo come sfruttare la classe Proxy per implementare il meccanismo di data binding.
Definiamo una classe Binder con un metodo bindTo()
come
mostrato di seguito:
class Binder {
bindTo(dataSourceObj, dataSourceProperty, dataTargetObj, dataTargetProperty) {
var bindHandler = {
set: function(target, property, newValue) {
if (property == dataSourceProperty) {
target[dataSourceProperty] = newValue;
dataTargetObj[dataTargetProperty] = newValue;
}
}
};
return new Proxy(dataSourceObj, bindHandler);
}
}
Il metodo
bindTo()
definisce una trap che cattura gli accessi in
scrittura al data source object, in modo tale che ogni
modifica alla proprietà specificata dal parametro
dataSourceProperty aggiorni la proprietà associata del
data target object. Il metodo bindTo()
restituisce il
proxy creato a partire dal data source object. Possiamo quindi
usare la classe Binder come nel seguente esempio:
var persona = {
nome: "Mario",
cognome: "Rossi"
};
var txtNome = document.getElementById("txtNome");
var binder = new Binder();
var personaConBinding = binder.bindTo(persona, "nome", txtNome, "value");
setTimeout(function() {
personaConBinding.nome = "Marco";
}, 5000);
Abbiamo creato il proxy dell’oggetto persona utilizzando il metodo bindTo()
della classe Binder. Nella chiamata al metodo bindTo()
abbiamo specificato che vogliamo mettere in relazione la
proprietà nome dell’oggetto persona con la proprietà value dell’elemento txtNome. In questo modo, ogni modifica
alla proprietà nome dell’oggetto personaConBinding si
rifletterà automaticamente sia sull’oggetto originario persona,
sia sulla casella di testo.
I due esempi di applicazione concreta che abbiamo riportato danno un’idea delle possibili utilizzi della classe Proxy nello sviluppo di applicazioni di una certa complessità.
Se vuoi aggiornamenti su JavaScript inserisci la tua email nel box qui sotto:
Compilando il presente form acconsento a ricevere le informazioni relative ai servizi di cui alla presente pagina ai sensi dell'informativa sulla privacy.
La tua iscrizione è andata a buon fine. Se vuoi ricevere informazioni personalizzate compila anche i seguenti campi opzionali:
Compilando il presente form acconsento a ricevere le informazioni relative ai servizi di cui alla presente pagina ai sensi dell'informativa sulla privacy.
I Video di HTML.it
Il DroidCon 2016, intervista a Francesco Ronchi
I numeri e i contenuti di DroidCon 2016, commentati dal padrone di casa Francesco Ronchi. Il capitano di Synesthesia fa […]