Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 41 di 112
  • livello avanzato
Indice lezioni

L'oggetto Reflect

Sfruttare l'oggetto Reflection, introdotto con le specifiche ECMA 2015, che unifica le funzionalità di reflection disponibili su Javascript.
Sfruttare l'oggetto Reflection, introdotto con le specifiche ECMA 2015, che unifica le funzionalità di reflection disponibili su Javascript.
Link copiato negli appunti

Reflect è un oggetto globale introdotto dalle specifiche ECMAScript 2015, che consente di eseguire operazioni di reflection sugli oggetti JavaScript, cioè di analizzare e manipolare programmaticamente le loro proprietà. Possiamo ad esempio definire una nuova proprietà o eliminarne una esistente, eseguire una funzione con uno specifico valore per this o verificare se un oggetto ha una determinata proprietà.

In realtà, anche le versioni di JavaScript che fanno capo alle specifiche ECMAScript 5 hanno già delle funzionalità per fare reflection sugli oggetti. Anzi, a dire il vero, alcune delle funzionalità messe a disposizione dall'oggetto Reflect si sovrappongono a quelle previste da ES5. Possiamo ad esempio usare Object.defineProperty() per creare una nuova proprietà e l'istruzione delete per eliminarne una esistente, giusto per fare qualche esempio.

Perché allora introdurre questo nuovo oggetto visto che buona parte delle funzionalità possono essere svolte con le specifiche precedenti?

Uno dei motivi principali per l'introduzione di Reflect è l'organizzazione delle API per la reflection. Come abbiamo visto nell'esempio appena riportato su Object.defineProperty() e delete, le funzioni per effettuare operazioni di reflection
sono disomogenee: alcune sono rappresentate da metodi di Object, altre da istruzioni del linguaggio. L'oggetto Reflect si pone l'obiettivo di centralizzare le funzionalità di reflection, oltre a migliorarne la gestione ed evitare alcuni potenziali malfunzionamenti presenti nelle versioni precedenti.

Verificare l'esistenza di una proprietà

Per illustrare meglio quest'ultimo punto, consideriamo brevemente il seguente codice:

var myObject = Object.create(null);
myObject.name = "objName";
console.log(myObject.hasOwnProperty(myObject, "name"));

L'esecuzione di questo codice genera giustamente un'eccezione, dal momento che l'oggetto myObject non eredita il metodo hasOwnProperty() da Object. Infatti esso è stato creato senza alcun prototipo di riferimento (Object.create(null)). In questo caso, se vogliamo conoscere l'esistenza o meno di una proprietà dell'oggetto, dovremmo far ricorso ad un approccio un po' più complesso, come quello mostrato dal seguente esempio:

Object.prototype.hasOwnProperty.call(myObject, "name");

Utilizzando l'oggetto Reflect possiamo scrivere molto più semplicemente:

Reflect.getOwnPropertyDescriptor(myObject, "name");

In questo caso otterremo il descrittore della proprietà (se esiste) o undefined. In alternativa possiamo utilizzare il metodo Reflect.has(), come mostrato di seguito:

Reflect.has(myObject, "name");

Il metodo restituisce true se l'oggetto ha la proprietà specificata come secondo parametro, oppure false in caso contrario. A differenza del metodo Reflect.hasOwnProperty(), Reflect.has() verifica l'esistenza della proprietà risalendo la catena dei prototipi, tenendo in considerazione quindi anche le proprietà ereditate.

Per ottenere l'elenco dei nomi delle proprietà non ereditate di un oggetto possiamo utilizzare il metodo Reflect.ownKeys(), come mostrato
dal seguente esempio:

var myObject = {id: 123, name: "objName"};
console.log(Reflect.ownKeys(myObject)); //["id", "name"]

Il metodo in questione prende come argomento l'oggetto che vogliamo analizzare e restituisce un array di stringhe che rappresentano i nomi di tutte le proprietà dell'oggetto.

Gestire le proprietà di un oggetto

Alcune delle funzionalità di reflection previste da Object sono state riportate quasi letteralmente come metodi dell'oggetto
Reflect. Ad esempio, mentre per definire una proprietà usiamo Object.defineProperty() in ES5, possiamo utilizzare il
metodo con lo stesso nome e la medesima sintassi a partire da ES1025:

var myObject = {};
 Reflect.defineProperty(myObject, "name", {
    value: "objName",
    writable: true,
    configurable: true,
    enumerable: true
 });

Il metodo Reflect.get() è utilizzato per ricavare il valore della proprietà di un oggetto. Il suo uso è abbastanza immediato, come possiamo vedere dal seguente esempio:

var myObject = {name: "objName"};
console.log(Reflect.get(myObject, "name")); // objName

Vediamo come il primo argomento del metodo rappresenta l'oggetto a cui siamo interessati, mentre il secondo argomento rappresenta la proprietà espressa come stringa.

Analogamente, per impostare un valore su una proprietà di un oggetto, faremo ricorso al metodo Reflect.set(), come mostrato di seguito:

var myObject = {name: "objName"};
Reflect.set(myObject, "name", "anotherName");
console.log(Reflect.get(myObject, "name")); //anotherName

In questo caso il metodo Reflect.set() richiede, oltre all'oggetto ed al nome della proprietà, il valore da assegnare. Se non specifichiamo alcun valore verrà assegnato undefined alla proprietà specificata, mentre se la proprietà non
esiste ne verrà creata una nuova.

I metodi Reflect.get() e Reflect.set() prevedono un ulteriore parametro opzionale che rappresenta il valore corrente per this da applicare quando le proprietà coinvolte sono definite tramite un getter o un setter.

Il metodo Reflect.deleteProperty() consente di eliminare una proprietà esistente di un oggetto. Esso ha la stessa funzionalità dell'istruzione delete, ma risulta molto più naturale e coerente, dal momento che si tratta di una operazione di reflection.
Sono previsti due argomenti, l'oggetto coinvolto e il nome della proprietà da eliminare, e restituisce un valore booleano che rappresenta l'esito dell'operazione. Il seguente esempio mostra come utilizzarlo:

var myObject = {name: "objName"};
console.log(Reflect.deleteProperty(myObject, "name")); //true

Gestire prototipi ed estensibilità

L'oggetto Reflect include anche alcune interessanti funzionalità di controllo dell'ereditarietà che conoscevamo già dalle specifiche ES5. Riassumiamole brevemente.

Il metodo Reflect.getPrototypeOf() restituisce il prototipo di un oggetto ed ha la medesima funzionalità di Object.getPrototypeOf(), come possiamo vedere dal seguente esempio:

var myObject = {name: "objName"};
console.log(Reflect.getPrototypeOf(myObject)); //Object {}

Come possiamo vedere, il prototipo del nostro oggetto è Object.

Reflect.setPrototypeOf() consente di assegnare un prototipo ad un oggetto, in modo analogo a Object.setPrototypeOf():

var myObject = {
    name: "objName"
 };
 var newObject = {
    id: 123
 };
 if (Reflect.setPrototypeOf(newObject, myObject)) {
    console.log(newObject.name); //objName
 }

A differenza di Object.setPrototypeOf(), il corrispondente metodo dell'oggetto Reflect restituisce un valore booleano che indica
se l'assegnamento del prototipo ha avuto successo (true) o meno (false). Nel caso in cui si tenta di assegnare un prototipo ad un valore primitivo, come ad esempio ad un numero o un valore booleano, viene generata una eccezione di tipo TypeError.

Reflect.setPrototypeOf(newObject, true); //Uncaught TypeError:...

Il metodo Reflect.preventExtensions() impedisce l'estensibilità di un oggetto, cioè la possibilità di aggiungere nuove
proprietà. La sua funzione è analoga all'omonimo metodo di Object, come possiamo vedere dal seguente codice:

var myObject = {
    name: "objName"
 };
 Reflect.preventExtensions(myObject);
 myObject.id = 123;
 console.log(myObject); //Object {name: "objName"}

Come possiamo vedere dall'esempio, il tentativo di aggiungere la proprietà id a myObject non ha avuto successo.

Infine, tramite il metodo Reflect.isExtensible() possiamo verificare se un oggetto è estensibile o meno:

var myObject = {name: "objName"};
Reflect.preventExtensions(myObject);
console.log(Reflect.isExtensible(myObject)); //false

Eseguire funzioni e costruttori

Oltre a consentire il controllo di oggetti, Reflect consente anche di gestire l'esecuzione di funzioni tramite i metodi Reflect.apply() e Reflect.construct().

Il metodo Reflect.apply() consente di invocare una funzione specificando un determinato valore per this. Il suo utilizzo corrisponde
all'invocazione di Function.prototype.apply(), ma ha il vantaggio di essere meno verboso e più semplice da utilizzare. Vediamolo con un esempio:

function sommaExtra(x, y) {
    return this + x + y;
 }
 var a = Function.prototype.apply.call(sommaExtra, 12, [4, 6]);
 console.log(a); //22
 var b = Reflect.apply(sommaExtra, 12, [4, 6]);
 console.log(b); //22

Come possiamo vedere, abbiamo definito la funzione sommaExtra() che somma il valore corrente di this con i due valori passati come argomento.

Questa funzione viene applicata sia tramite Function.prototype.apply() che tramite Reflect.apply(), mostrando come quest'ultimo approccio è molto più compatto ed intuitivo.

Entrambi gli approcci prevedono tre parametri:

  • la funzione da eseguire;
  • il valore da impostare per this (opzionale);
  • un array di valori che rappresentano i parametri da passare alla funzione (opzionale).

Un altro interessante metodo è Reflect.construct(), che consente di invocare un costruttore, incluse le classi, passando un insieme di argomenti. Il risultato ottenuto da Reflect.construct() è analogo a quello ottenuto dall'operatore new o dal metodo Object.create(), ma il metodo di Reflect fa in modo che il prototipo dell'oggetto creato corrisponda al prototipo di un altro costruttore.

Vediamo di chiarire il concetto con un esempio. Supponiamo di avere la seguente classe che definisce un'automobile:

class Automobile {
    constructor(modello, colore) {
       this.modello = modello;
       this.colore = colore;
    }
 }

Ora supponiamo di definire una funzione che genera automobili utilizzando Object.create(), cioè una factory:

function produciAuto(modello, colore) {
    var auto = Object.create(Automobile.prototype);
    Automobile.call(auto, modello, colore);
    return auto;
 }

In questo caso abbiamo bisogno di creare l'oggetto auto in due passi: il primo passo crea l'istanza specificando il prototipo di riferimento, mentre il secondo passo invoca il costruttore passando l'istanza appena creata come valore di this.

Possiamo semplificare i due passaggi con uno solo utilizzando Reflect.construct(), come possiamo vedere dal seguente esempio:

function produciAuto(modello, colore) {
    return Reflect.construct(Automobile, [modello, colore], Automobile);
}

Il metodo Reflect.construct() prevede tre parametri:

  • il costruttore o la classe da utilizzare per creare l'oggetto;
  • un array di parametri da passare al costruttore;
  • una classe o un costruttore il cui prototipo verrà utilizzato per assegnarlo al nuovo oggetto. Questo parametro è opzionale;, e se non viene specificato viene utilzzato lo stesso prototipo del primo parametro.

Alla luce di quanto detto per il terzo parametro, possiamo riscrivere l'ultimo esempio di codice come segue:

function produciAuto(modello, colore) {
    return Reflect.construct(Automobile, [modello, colore]);
 }

Ti consigliamo anche