Come verificare se un oggetto possiede una proprietà in Javascript

Nelle lezioni precedenti abbiamo visto che gli oggetti Javascript sono aggregati o insiemi di proprietà. In questa lezione vediamo come sia possibile verificare la presenza o meno di una proprietà in un oggetto Javascript.

Operatore in

Verificare la presenza di una proprietà è spesso una necessità molto utile che si presenta nello sviluppo dei programmi.

Il metodo più semplice di verifica è utilizzare l'operatore in. Vediamo un esempio:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let A = {
    x: 1
};

let B = Object.create(A);
B.y = 2;

"y" in B;   // Restituisce true:  'y' è una own property di B
"x" in B;   // Restituisce true:  'x' è una proprietà del prototipo A di B
"z" in B;   // Restituisce false: 'z' non è una proprietà né di B né di A
Come si osserva dall'esempio, l'operatore in prevede due operandi: una stringa che identifica la proprietà e l'oggetto in cui cercarla. Restituisce false se la proprietà non è presente, mentre restituisce true se essa è una proprietà dell'oggetto o di uno dei prototipi della sua catena.

Metodo hasOwnProperty

La seconda tecnica di verifica consiste nell'utilizzo del metodo hasOwnProperty.

Questo metodo, in realtà, è un metodo dell'oggetto Object e, pertanto, viene ereditato da quasi tutti gli oggetti (a meno che il loro prototipo non è null). Infatti, come abbiamo visto nella lezione relativa alla creazione degli oggetti, quasi tutti gli oggetti hanno come prototipo l'oggetto Object.

Attraverso questo metodo, che richiede come parametro una stringa contenente il nome della proprietà da ricercare, è possibile verificare la presenza o l'assenza di una proprietà. La differenza rispetto all'operatore in, tuttavia, è che questo metodo restituisce true solo per le own properties (come era intuibile dal nome). Vediamo, infatti, l'esempio:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let A = {
    x: 1
};

let B = Object.create(A);
B.y = 2;

B.hasOwnProperty("y");   // Restituisce true:  'y' è una own property di B
B.hasOwnProperty("x");   // Restituisce false: 'x' non è una own property di B
B.hasOwnProperty("z");   // Restituisce false: 'z' non è una proprietà di B

Metodo propertyIsEnumerable

Analogamente al metodo hasOwnProperty esiste anche il metodo propertyIsEnumerable che viene ereditato sempre da Object. Il suo funzionamento è identico a hasOwnProperty, l'unica differenza consiste nel fatto che restituisce false se la own property non è enumerable. Per capire cosa sia una proprietà numerabile o enumerable rimandiamo alla prossima lezione. In generale tutte le proprietà sono enumerabili a meno che non si utilizzino le metaclassi, ma questo è un argomento delle prossime lezioni. Per questo motivo, per il momento, possiamo considerare i due metodi, hasOwnProperty e propertyIsEnumerable, sostanzialmente identici.

Accesso diretto

La tecnica finale per verificare l'esistenza o meno di una proprietà è il semplice accesso diretto. Anzichè richiederne l'esistenza, si può richiedere direttamente la proprietà e verificare che essa sia diversa da undefined. Vediamo un esempio:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let A = {
    x: 1
};

let B = Object.create(A);
B.y = 2;

B.y !== undefined;   // Restituisce true:  'y' è una own property di B
B.x !== undefined;   // Restituisce true:  'x' è una proprietà del prototipo A di B
B.z !== undefined;   // Restituisce false: 'z' non è una proprietà di B

Vi è una sottile differenza tra questa tecnica e l'operatore in. Utilizzando l'operatore in è in grado di distinguere tra l'esistenza di una proprietà che è stata impostata a undefined ed una proprietà che non esiste. Vediamo l'esempio:

1
2
3
4
5
6
7
8
let A = {
    x: 1
    y: undefined
};

A.y !== undefined;    // Restituisce false: anche se la proprietà 'y' esiste
"y" in A;             // Restituisce true:  la proprietà 'y' esiste anche
                      // se impostata a undefined

Errori di accesso alle proprietà

In generale, accedere ad una proprietà di un oggetto che non esiste (come abbiamo fatto sopra) non è un errore:

1
2
3
4
5
6
let A = {
    x: 1
};

let result = A.y;    // result = undefined, in quanto la proprietà 'y'
                     // non esiste

Nell'esempio di sopra, la variabile result varrà undefined in quanto abbiamo provato ad accedere alla proprietà y di A, ma questa proprietà non esiste. L'interprete non lancerà eccezioni ma, anzi, l'esecuzione proseguirà come se nulla fosse accaduto.

Anche se a prima vista questo comportamento dell'interprete sembra strano o contraddittorio (in altri linguaggi verrebbe generata un'eccezione), esso è voluto. Ricordiamo, infatti, che gli oggetti in Javascript sono dinamici. Le proprietà possono essere aggiunge, rimosse e modificate a runtime. Pertanto, inserire un meccanismo di controllo che, ad ogni accesso in lettura, verifica l'esistenza o meno di una proprietà rallenterebbe in maniera considerevole l'esecuzione. Per questo motivo, i progettisti dello standard Javascript hanno optato per questo comportamento.

Ovviamente bisogna fare attenzione. Infatti, nel caso di sopra, non si presentano particolari problemi. Ma se la proprietà dell'oggetto fosse a sua volta un oggetto? Proviamo con un esempio:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let A = {
    x: 1,
    y: {
        z: 2
    }
};

let B = {};

let result1 = A.y.z;    // Nessun errore. A possiede la proprietà y che a sua
                        // volta possiede la proprietà z
let result2 = B.y.z;    // Errore: la proprietà y non esiste, quindi è undefined.
                        // Ma è un errore richiedere una proprietà di undefined.

In questo esempio abbiamo l'oggetto composto A di cui richiediamo la proprietà y per poi richiedere la proprietà z. Poiché effettivamente A possiede una proprietà y nessuna eccezione viene lanciata. Nel caso di B, invece, dato che richiederne la proprietà y restituisce undefined, nel momento in cui richiediamo z un'eccezione viene lanciata. Infatti è un errore in Javascript richiedere una proprietà di undefined.

Potremmo risolvere il problema in questo modo:

1
2
3
4
let result2 = undefined;
if (B.y !== undefined) {
    result2 = B.y.z;
}

Tuttavia è una soluzione prolissa e prona agli errori. Un modo più semplice per risolvere il problema è scrivere la seguente riga:

let result2 = B.y && B.y.z;

A prima vista, questa riga di codice può sembrare criptica. Ma ricordando come funzionano gli operatori booleani in Javascript, si può capire perché funziona. Infatti:

  • Gli operatori booleani in Javascript sono cortocircuitati. Ciò significa che in questo caso in cui abbiamo l'operatore && (and) se il primo operando è falso, l'interprete non valuterà il secondo in quanto è inutile. Per questo se B.y è undefined (che in Javascript è assimilabile a false), il secondo operando B.y.z non verrà valutato e non verrà generata un'eccezione. Inoltre, dato che in Javascript qualsiasi valore può essere considerato un booleano, quando l'operatore di sinistra di && è assimilabile a false, ne verrà restituito il risultato che, nel nostro caso, è undefined.
  • Viceversa, se il primo operando è assimilabile a true, come ad esempio se B avesse effettivamente una proprietà y, allora, in tal caso, verrebbe valutato il secondo operando e restituito il risultato. Per cui, se B.y.z fosse undefined otterremmo undefined, mentre se fosse diverso ne otterremmo il valore.

ECMAScript 2020 introduce un nuovo operatore: ?. che prende il nome di operatore di accesso condizionale. Utilizzando tale operatore possiamo riscrivere la riga di sopra, in maniera ancora più sintetica, come:

let result2 = B?.y?.z;

Riassumendo

In questa lezione abbiamo visto come verificare l'esistenza di una proprietà di un oggetto attraverso l'operatore in e i due metodi hasOwnProperty e propertyIsEnumerable. Inoltre abbiamo visto come, attraverso l'accesso diretto, sia possibile effettuare questa verifica. Sempre nel caso dell'accesso diretto abbiamo visto come usare gli operatori booleani cortocircuitati e l'operatore di accesso condizionale.

Nella prossima lezione vedremo il concetto di proprietà enumerabili e come enumerare o elencare tutte le proprietà di un oggetto.