Il problema: l'accesso annidato è fragile
Entrare in un oggetto annidato va benissimo, finché uno dei livelli non c'è:
user.address è undefined, e leggere .city da undefined lancia TypeError: Cannot read properties of undefined. I dati reali (risposte API, JSON analizzato, query sul DOM) sono pieni di campi che potrebbero esistere oppure no, e scrivere controlli difensivi per ogni livello diventa presto rumoroso:
const city = user && user.address && user.address.city;
Quasi leggibile con due livelli. Doloroso con quattro. L'operatore ?. è la soluzione più pulita.
?. va in cortocircuito su null e undefined
L'optional chaining legge una proprietà solo se il valore che la precede non è null o undefined. Se lo è, l'intera espressione vale undefined e si ferma.
La prima riga legge name normalmente. La seconda arriva a user.address, trova undefined e si arrende: il resto della catena non viene mai eseguito. La terza lancerebbe un errore senza ?., perché .toUpperCase() verrebbe chiamato su undefined; con ?. tutto vale semplicemente undefined, senza rumore.
Il modello mentale: ?. significa "se ciò che mi precede è null o undefined, lascia perdere e restituisci undefined, altrimenti vai avanti."
Funziona anche con array e chiamate di funzione
Tre sintassi, stessa idea:
?.[...]per accedere ad array o a proprietà dinamiche.?.()per chiamare qualcosa che potrebbe essere una funzione oppure no.?.nameper il normale accesso alle proprietà.
Tutte e tre vanno in cortocircuito allo stesso modo. user?.notAMethod?.() non lancia errori anche se notAMethod non esiste: il secondo ?. vede undefined e si ferma.
È particolarmente comodo con le callback opzionali:
Niente più controlli if (typeof onDone === "function") prima di ogni chiamata.
Solo null e undefined attivano il cortocircuito
Questo è il dettaglio che sorprende molti. A ?. interessano solo i valori nullish. Gli altri valori falsy, cioè 0, "", false, NaN, sono oggetti perfettamente validi su cui concatenare (almeno nella misura in cui l'autoboxing lo consente):
data.count è 0, che è falsy ma non nullish, quindi ?.toFixed(2) viene eseguito e restituisce "0.00". Confrontalo con &&:
La versione con && restituisce 0 perché data.count è falsy e interrompe la valutazione. La versione con ?. restituisce "0.00" perché 0 non è nullish. Se vuoi davvero fermarti su 0, && è la scelta giusta. Se vuoi fermarti solo quando "il valore manca", ti serve ?..
Conta dove metti il ?
?. protegge il valore che viene prima, non quello dopo. Quindi mettilo sul livello che potrebbe mancare:
Qui funzionano tutte e tre perché in realtà non manca niente. Ma se config.server potesse essere undefined, ti servirebbe config.server?.host: un ?. prima di server non aiuterebbe, perché il problema è leggere .host da un server che non c'è.
Una buona regola: metti ?. su ogni livello in cui il valore prima di esso potrebbe essere davvero nullish. Spargere ?. su ogni punto "per sicurezza" nasconde i bug e rende il codice più rumoroso.
Assegnare tramite ?. non funziona
L'optional chaining è solo in lettura. Non puoi usarlo come destinazione di un'assegnazione:
user?.address?.city = "Paris"; // SyntaxError
Ha senso, se ci pensi: cosa vorrebbe dire assegnare a una proprietà di undefined? Se devi impostare un valore solo quando esiste l'oggetto genitore, scrivilo per esteso:
Quando usarlo, e quando no
?. dà il meglio quando un valore è legittimamente opzionale:
- Risposte API in cui alcuni campi possono esserci oppure no.
- Ricerche nel DOM:
document.querySelector(".banner")?.remove(). - Callback che potrebbero non essere passate:
options.onError?.(err). - Chiamate a librerie concatenate in cui i risultati intermedi possono essere
null.
È lo strumento sbagliato quando il valore dovrebbe esistere sempre. Spargere ?. per zittire gli errori in quel caso trasforma un bug rumoroso e facile da trovare ("TypeError alla riga 42") in uno silenzioso (una variabile misteriosamente undefined tre funzioni più in là). Quando qualcosa deve esserci, lascia che lanci l'errore: lo stack trace ti sta facendo un favore.
Un esempio realistico
Estrarre un valore da una risposta API potenzialmente incompleta:
Ogni ?. gestisce un livello di incertezza. avatarUrl finisce per valere undefined in modo pulito. onClick?.() chiama l'handler se per caso c'è.
Avrai notato il ?? nella riga di displayName: è l'altra metà di questo pattern. ?. ti dà undefined quando qualcosa manca; ?? ti permette di sostituirlo con un default senza inciampare su valori falsy legittimi come 0 o "".
Prossimo passo: nullish coalescing
?. e ?? sono la stessa idea applicata a problemi diversi: entrambi trattano null e undefined come "mancante" e lasciano stare gli altri valori falsy. Nella prossima pagina vediamo come ?? ti dà valori di default sensati, e perché || da anni sbaglia in silenzio proprio su questo.
Domande frequenti
Cosa fa ?. in JavaScript?
L'operatore ?. accede a una proprietà, a un indice di array o a un metodo solo se il valore che lo precede non è null o undefined. Se lo è, l'intera espressione va in cortocircuito e vale undefined invece di lanciare un errore. user?.address?.city non si rompe quando mancano user o address.
Quando conviene usare l'optional chaining in JavaScript?
Usa ?. quando un valore può legittimamente mancare: risposte API con campi opzionali, ricerche nel DOM che potrebbero non trovare un nodo, callback che potrebbero essere passate oppure no. Non usarlo per coprire bug in cui un valore dovrebbe esistere sempre: in quei casi un valore mancante è un'informazione reale che vuoi vedere.
Che differenza c'è tra ?. e &&?
Nella maggior parte dei casi danno lo stesso risultato, ma ?. va in cortocircuito solo su null o undefined, mentre && lo fa su qualsiasi valore falsy, compresi 0, '' e false. obj && obj.count restituisce 0 quando count è 0; anche obj?.count restituisce 0, ma la catena si ferma in modo pulito solo sui valori nullish.
Si può usare l'optional chaining con array e chiamate di funzione?
Sì. arr?.[0] legge in sicurezza un indice dell'array e fn?.() chiama una funzione solo se esiste. Entrambi seguono la stessa regola: se il valore prima di ?. è null o undefined, l'espressione vale undefined e non viene eseguito nient'altro.