Il concatenamento opzionale (optional chaining), ?., è un modo sicuro di accedere alle proprietà annidate di un oggetto, anche nel caso in cui una proprietà intermedia non dovesse esistere.
Il problema della “proprietà inesistente”
Se avete appena cominciato a leggere questo tutorial e a imparare JavaScript, forse questo problema non lo avete ancora affrontato, ma è piuttosto comune.
Ad esempio, ipotizziamo di avere un oggetto user, in cui sono memorizzate le informazioni relative ai nostri utenti.
La maggior parte dei nostri utenti possiedono l’indirizzo nella proprietà user.address, la via in user.address.street, ma qualcuno potrebbe non averle fornite.
In questo caso, quando proviamo ad accedere a user.address.street, e l’utente non possiede un indirizzo, avremo un errore:
Questo è il risultato che ci si aspetta. JavaScript funziona in questo modo. Se user.address è undefined, un tentativo di accesso a user.address.street fallirà con un errore.
Nella maggior parte dei casi, preferiremmo avere undefined piuttosto di un errore (in questo caso con il significato “nessuna via”).
… Un altro esempio. Il metodo document.querySelector('.elem') ritorna un oggetto che corrisponde ad un elemento della pagina web, che ritorna null quando l’elemento non esiste.
Di nuovo, se un elemento non esiste, otterremo un errore nel tentativo di accedere a .innerHTML di null. In alcuni casi, in cui l’assenza di un elemento è normale, vorremo evitare l’errore e accettare come risultato html = null.
Come possiamo farlo?
La soluzione più ovvia sarebbe di controllare il valore utilizzando if o l’operatore condizionale ? prima di accedere alle proprietà, come nell’esempio:
let user = {};
alert(user.address ? user.address.street : undefined);
Funziona, nessun errore… Ma è poco elegante. Come potete vedere , "user.address" appare due volte nel codice. Per proprietà molto più annidate, potrebbe diventare un problema, in quanto saranno necessarie molte più ripetizioni.
Ad esempio, proviamo a recuperare il valore di user.address.street.name.
Dobbiamo verificare sia user.address che user.address.street:
let user = {}; // l'utente non ha address
alert(user.address ? user.address.street ? user.address.street.name : null : null);
Questo è semplicemente terribile, un codice del genere potrebbe essere difficile da comprendere.
Ci sarebbe un modo migliore per riscriverlo, utilizzando l’operatore &&:
Concatenare con && l’intero percorso verso la proprietà ci assicura che tutti i componenti esistano (in caso contrario, la valutazione si interrompe), ma non è comunque l’ideale.
Come potete vedere, i nome delle proprietà sono ancora duplicate nel codice. Ad esempio, nel codice sopra, user.address è ripetuto tre volte.
Questo è il motivo per cui la concatenazione opzionale ?. è stata aggiunta al linguaggio. Per risolvere questo problema una volta per tutte!
Concatenazione opzionale
La concatenazione opzionale ?. interrompe la valutazione se il valore prima di ?. è undefined o null, e ritorna undefined.
D’ora in poi, in questo articolo, per brevità diremo che qualcosa “esiste” se non è né null né undefined.
In altre parole, value?.prop:
- funziona come
value.prop, sevalueesiste, - altrimenti (quando
valueèundefined/null) ritornaundefined.
Vediamo un modo sicuro per accedere a user.address.street utilizzando ?.:
Il codice è corto e pulito, non c’è alcuna duplicazione.
Leggere l’indirizzo con user?.address funzionerebbe anche se l’oggetto user non esistesse:
Da notare: la sintassi ?. rende opzionale il valore che la precede, nulla di più.
Ad esempio in user?.address.street.name il costrutto ?. permette alla proprietà user di essere null/undefined in sicurezza (e ritornare undefined in questo caso), ma questo vale solamente per user. Si accederà alle altre proprietà normalmente. Se vogliamo che anche altre proprietà siano opzionali, dobbiamo rimpiazzare . con ?..
Dovremmo utilizzare ?. solamente quando va bene che una proprietà possa non esistere.
Ad esempio, considerando la logica del nostro codice, l’oggetto user deve necessariamente esistere, mentre address è opzionale, quindi dovremmo scrivere user.address?.street, non user?.address?.street.
Quindi, se user dovesse essere undefined per errore, otterremo un errore e potremmo sistemarlo. Altrimenti, gli errori di programmazione potrebbero essere silenziati in modo non appropriato, rendendo il debug molto difficile.
?. deve essere dichiarataSe non esiste alcuna variabile user, allora user?.anything provocherà un errore:
La variabile deve essere dichiarata (ad esempio come let/const/var user o come parametro di funzione). La concatenazione opzionale funziona solamente con le variabili dichiarate.
Corto circuito
Come detto in precedenza, il costrutto ?. interrompe immediatamente (manda in “corto circuito”) la valutazione se la proprietà a destra non esiste.
Quindi, nel caso ci siano ulteriori chiamate a funzione o side-effects, questi non verranno eseguiti.
Ad esempio:
Altre varianti: ?.(), ?.[]
La concatenazione opzionale ?. non è un operatore, ma uno speciale costrutto sintattico, che funziona anche con le funzioni e le parentesi quadre.
Ad esempio, ?.() viene utilizzato per invocare una funzione che potrebbe non esistere.
Nel codice sotto, alcuni dei nostri utenti possiedono il metodo admin, mentre altri no:
Qui, in entrambe le righe, come prima cosa abbiamo utilizzato il punto (user1.admin) per ottenere la proprietà admin, poiché l’oggetto user deve necessariamente esistere, quindi l’accesso è sicuro.
Successivamente ?.() controlla la parte sinistra: se la funzione admin esiste, allora viene eseguita (ciò che accade con user1). Altrimenti (con user2) la valutazione si interrompe senza errori.
La sintassi ?. funziona anche con le parentesi [] (invece del punto .). Come nei casi precedenti, possiamo accedere con sicurezza alla proprietà di un oggetto che potrebbe non esistere.
Possiamo anche utilizzare ?. con delete:
?. per l’accesso e la rimozione sicura, ma non per la scritturaRiepilogo
La concatenazione opzionale ?. ha tre forme:
obj?.prop– ritornaobj.propseobjesiste, altrimenti ritornaundefined.obj?.[prop]– ritornaobj[prop]seobjesiste, altrimenti ritornaundefined.obj.method?.()– invocaobj.method()seobj.methodesiste, altrimenti ritornaundefined.
Come possiamo vedere, le tre forme sono semplici da utilizzare. Il costrutto ?. verifica che la parte sinistra non sia null/undefined; se non lo è, permette alla valutazione di proseguire, altrimenti la interrompe immediatamente.
La concatenazione di ?. permette di accedere in sicurezza a proprietà annidate.
In ogni caso, dovremmo applicare ?. con prudenza, solamente nei casi in cui è accettabile che la parte sinistra possa non esistere. In questo modo evitiamo di nascondere errori di programmazione, nel caso ce ne siano.