Differenze tra null e undefined in JavaScript: Guida Completa per Principianti

Principiante
JavaScript

Scopri una volta per tutte la differenza tra null e undefined in JavaScript, come evitarne l'uso improprio e come gestirli correttamente nel tuo codice.

Pubblicato
Tag
Web Development Beginner javascript coding-tips null vs undefined

Introduzione: Il dilemma dell'assenza di valore

Se stai iniziando il tuo percorso nella programmazione web con JavaScript, ti sarai accorto che il linguaggio ha un modo peculiare di gestire l'"assenza" di dati. A differenza di altri linguaggi dove esiste un unico concetto di valore vuoto, JavaScript introduce due entità distinte: null e undefined.

Per un principiante, questa distinzione può sembrare superflua o addirittura confusionaria. "Se entrambi significano che non c'è nulla, perché ne servono due?". La risposta risiede nella semantica: undefined rappresenta qualcosa che non è stato ancora definito, mentre null rappresenta un'assenza intenzionale di valore. Capire questa sottile differenza è fondamentale per evitare bug silenziosi, crash dell'applicazione (come il famigerato TypeError: cannot read property of undefined) e per scrivere codice più leggibile e professionale.

In questa guida analizzeremo nel dettaglio cosa sono, quando appaiono nel tuo codice e come gestirli utilizzando le moderne sintassi di JavaScript.

Cos'è undefined? L'assenza involontaria

In JavaScript, undefined è un tipo di dato primitivo che indica che una variabile è stata dichiarata, ma a cui non è ancora stato assegnato alcun valore. Possiamo pensarlo come un "segnaposto" che il motore di JavaScript inserisce automaticamente quando non sa cosa mettere in una determinata posizione.

Quando si verifica undefined?

Esistono diversi scenari comuni in cui incontrerai undefined:

  1. Variabili dichiarate senza inizializzazione: Quando usi let o var per dichiarare una variabile ma non le assegni un valore tramite l'operatore =.
  2. Proprietà di oggetti inesistenti: Se provi ad accedere a una chiave di un oggetto che non è stata definita.
  3. Funzioni senza valore di ritorno: In JavaScript, ogni funzione restituisce qualcosa. Se non scrivi esplicitamente un'istruzione return, la funzione restituirà automaticamente undefined.
  4. Parametri di funzione omessi: Se definisci una funzione che accetta tre parametri, ma ne passi solo due durante la chiamata, il terzo parametro sarà undefined.

Ecco un esempio pratico per illustrare questi casi:

// 1. Variabile dichiarata ma non inizializzata
let utente;
console.log(utente); // Output: undefined

// 2. Proprietà inesistente in un oggetto
const profilo = { nome: "Mario", eta: 30 };
console.log(profilo.email); // Output: undefined

// 3. Funzione senza return
function saluta() {
    console.log("Ciao!");
}
let risultato = saluta();
console.log(risultato); // Output: undefined

// 4. Parametro mancante
function somma(a, b) {
    console.log("Valore di b:", b);
    return a + b;
}
somma(10); // Output: Valore di b: undefined

Nel codice sopra, JavaScript non genera un errore immediato quando incontra undefined, ma continuerà l'esecuzione. Il problema sorge quando cerchi di eseguire un'operazione su undefined (ad esempio, chiamare un metodo su di esso), causando l'arresto del programma.

Cos'è null? L'assenza intenzionale

A differenza di undefined, null non viene mai assegnato automaticamente dal motore di JavaScript. null è un valore che lo sviluppatore assegna deliberatamente a una variabile per indicare che quella variabile è vuota o che non contiene alcun oggetto.

Perché usare null?

Usiamo null per comunicare un'intenzione. Se una variabile è null, stiamo dicendo al resto del programma: "So che questa variabile dovrebbe contenere un oggetto o un valore, ma in questo momento è volutamente vuota".

È estremamente utile nei seguenti casi:

  • Reset di variabili: Quando vuoi "svuotare" una variabile che prima conteneva un oggetto complesso.
  • Risposte API: Quando un server risponde che un dato non esiste (es. un utente che cerca un profilo privato), spesso viene restituito null per indicare che la ricerca è avvenuta correttamente ma il risultato è nullo.
  • Stati iniziali: Impostare un valore a null all'inizio di un'applicazione per indicare che il dato verrà caricato successivamente (es. let currentUser = null;).
// Assegnazione intenzionale
let sessioneUtente = null; 

// Simulazione di un caricamento
function caricaUtente(id) {
    if (id === 1) {
        return { nome: "Alice" };
    } else {
        return null; // Indichiamo esplicitamente che l'utente non è stato trovato
    }
}

const utenteTrovato = caricaUtente(999);
if (utenteTrovato === null) {
    console.log("L'utente non esiste nel database.");
}

Il grande equivoco: typeof null

Se provi a controllare il tipo di undefined usando l'operatore typeof, otterrai esattamente ciò che ti aspetti:

console.log(typeof undefined); // "undefined"

Tuttavia, se provi a fare lo stesso con null, accadrà qualcosa di strano:

console.log(typeof null); // "object"

Perché null viene identificato come un oggetto? Questa è una delle bug più famose della storia di JavaScript. Nelle prime versioni del linguaggio, i valori venivano rappresentati internamente con dei tag di tipo. Il tag per gli oggetti era 0, e poiché null era rappresentato da un puntatore nullo (che in molti sistemi è zero), typeof ha iniziato a restituirlo come "object".

Questo errore non è mai stato corretto perché farlo romperebbe milioni di siti web esistenti che si basano su questo comportamento. Pertanto, ricorda: non usare typeof per verificare se un valore è null.

Come confrontare e gestire null e undefined

Data la loro somiglianza, JavaScript offre due modi per confrontarli: l'uguaglianza astratta (==) e l'uguaglianza stretta (===).

Uguaglianza Astratta (Loose Equality)

L'operatore == esegue una conversione di tipo prima del confronto. In JavaScript, null e undefined sono considerati "equivalenti" in questo senso.

console.log(null == undefined); // true

Questo comportamento è utile se vuoi verificare se una variabile è "vuota", indipendentemente dal fatto che sia null o undefined.

Uguaglianza Stretta (Strict Equality)

L'operatore === non converte i tipi. Poiché null e undefined sono tipi primitivi diversi, il risultato sarà false.

console.log(null === undefined); // false

Gestione Moderna: Nullish Coalescing e Optional Chaining

Scrivere continuamente if (valore !== null && valore !== undefined) è noioso e rende il codice verboso. JavaScript moderno (ES2020+) ha introdotto due operatori fondamentali per semplificare questo processo.

1. L'operatore Nullish Coalescing (??)

L'operatore ?? restituisce l'operando di destra quando l'operando di sinistra è null o undefined, ma non quando è un altro valore "falsy" come 0 o una stringa vuota "".

let impostazioniUtente = 0; // L'utente ha impostato il volume a 0

// Vecchio metodo con l'operatore OR (||)
let volume1 = impostazioniUtente || 50; 
console.log(volume1); // 50 (Errore! 0 è falsy, quindi prende il default)

// Nuovo metodo con ??
let volume2 = impostazioniUtente ?? 50;
console.log(volume2); // 0 (Corretto! 0 non è null né undefined)

2. L'operatore Optional Chaining (?.)

L'operatore ?. permette di accedere a proprietà di un oggetto senza dover verificare manualmente se l'oggetto stesso esiste. Se l'oggetto prima del punto è null o undefined, l'espressione restituisce undefined invece di lanciare un errore.

const utente = { 
    profilo: { 
        nome: "Luca" 
    } 
};

// Senza optional chaining: rischio crash se 'profilo' è assente
// console.log(utente.contatti.email); // TypeError!

// Con optional chaining: restituisce undefined in modo sicuro
console.log(utente.contatti?.email); // undefined

Esempi pratici e casi d'uso reali

Vediamo come applicare queste conoscenze in uno scenario di sviluppo web reale: la gestione dei dati provenienti da un'API.

Immaginiamo di ricevere un oggetto utente che potrebbe avere dei campi mancanti o esplicitamente nulli.

function processUserData(apiResponse) {
    // Caso 1: L'API non ha restituito il campo 'bio' (sarà undefined)
    // Caso 2: L'utente ha cancellato la bio (sarà null)
    // Vogliamo mostrare "Non ancora disponibile" in entrambi i casi
    const bio = apiResponse.bio ?? "Bio non ancora disponibile";

    // Caso 3: Vogliamo accedere a un dato annidato in modo sicuro
    const city = apiResponse.address?.city ?? "Città sconosciuta";

    return {
        bio: bio,
        city: city
    };
}

const response1 = { nome: "Anna", address: { city: "Roma" } }; // bio è undefined
const response2 = { nome: "Marco", bio: null, address: null }; // bio è null, address è null

console.log(processUserData(response1)); // { bio: "Bio non ancora disponibile", city: "Roma" }
console.log(processUserData(response2)); // { bio: "Bio non ancora disponibile", city: "Città sconosciuta" }

In questo esempio, l'uso di ?? e ?. ci permette di gestire l'incertezza dei dati esterni in modo elegante, evitando che l'applicazione si blocchi se l'API cambia struttura o restituisce valori nulli.

Errori comuni e FAQ

Perché non dovrei usare || per i valori di default?

Molti sviluppatori usano l'operatore OR (||) per assegnare valori di default. Tuttavia, || controlla se il valore è "falsy". In JavaScript, i valori falsy includono false, 0, "" (stringa vuota), NaN, null e undefined. Se il tuo valore legittimo è 0 o una stringa vuota, || lo ignorerà e userà il default, creando bug logici difficili da scovare.

Posso convertire null in undefined?

Sì, ma raramente è necessario. Se vuoi uniformare i valori, puoi usare l'operatore ?? o un semplice controllo. Tuttavia, è meglio mantenere la distinzione semantica: undefined per ciò che non è stato toccato, null per ciò che è stato deliberatamente svuotato.

Qual è il modo più veloce per controllare se una variabile è "vuota"?

Se vuoi che sia null che undefined vengano considerati "vuoti", usa l'uguaglianza astratta: if (variabile == null) { ... } Questo singolo controllo copre entrambi i casi grazie alla peculiarità di JavaScript menzionata in precedenza.

Prossimi passi

Ora che hai compreso la differenza tra null e undefined, sei pronto a scrivere codice JavaScript più robusto. Per approfondire ulteriormente la gestione dei tipi e dei valori in JavaScript, ti suggerisco di studiare i seguenti argomenti:

  1. Tipi di dati primitivi: Approfondisci la differenza tra String, Number, Boolean, BigInt, Symbol e i due che abbiamo visto oggi.
  2. Truthiness e Falsiness: Impara quali valori JavaScript considera "veri" o "falsi" all'interno di un blocco if.
  3. TypeScript: Se vuoi eliminare l'incertezza di null e undefined, prova TypeScript. Ti permette di definire esplicitamente se una variabile può essere nulla o meno tramite i "Union Types" (es. string | null), costringendoti a gestire i casi limite a tempo di compilazione anziché a tempo di esecuzione.
  4. Destructuring con valori di default: Scopri come assegnare valori di default direttamente durante l'estrazione di proprietà da un oggetto o elementi da un array.

Ricorda: la programmazione è fatta di dettagli. Gestire correttamente l'assenza di valore non è solo una questione di sintassi, ma di chiarezza d'intento nel tuo software.