Mutabilità e Immutabilità in Javascript

Concetti Chiave
  • I valori primitivi in Javascript (come undefined, null, booleani, numeri e stringhe) sono immutabili: non possono essere cambiati una volta creati.
  • Gli oggetti (inclusi array e funzioni) sono mutabili: i loro valori possono essere cambiati dopo la loro creazione.
  • I primitivi sono confrontati per valore: due valori sono uguali solo se hanno lo stesso valore.
  • Gli oggetti sono confrontati per riferimento: due oggetti sono uguali solo se si riferiscono allo stesso oggetto sottostante.
  • Assegnare un oggetto a una variabile assegna il riferimento all'oggetto, non una copia dell'oggetto stesso.
  • Per creare una copia di un oggetto o array, è necessario copiare esplicitamente le proprietà o gli elementi.
  • Per confrontare due oggetti o array distinti, si devono confrontare le loro proprietà o elementi.

Valori Primitivi Immutabili e Riferimenti a Oggetti Mutabili

Esiste una differenza fondamentale in JavaScript tra valori primitivi (undefined, null, booleani, numeri e stringhe) e oggetti (inclusi array e funzioni).

I primitivi sono immutabili: non c'è modo di cambiare (o "mutare") un valore primitivo. Questo è ovvio per numeri e booleani: non ha nemmeno senso cambiare il valore di un numero. Non è così ovvio per le stringhe, tuttavia. Poiché le stringhe sono come array di caratteri, si potrebbe aspettare di poter alterare il carattere a qualsiasi indice specificato. In effetti, JavaScript non lo permette, e tutti i metodi delle stringhe che sembrano restituire una stringa modificata stanno, in realtà, restituendo un nuovo valore stringa. Per esempio:

let s = "ciao";   // Iniziamo con del testo in minuscolo
s.toUpperCase();   // Restituisce "CIAO", ma non altera s
s                  // => "ciao": la stringa originale non è cambiata

I primitivi sono anche confrontati per valore: due valori sono uguali solo se hanno lo stesso valore. Questo suona circolare per numeri, booleani, null, e undefined: non c'è altro modo in cui potrebbero essere confrontati. Ancora una volta, tuttavia, non è così ovvio per le stringhe. Se due valori stringa distinti sono confrontati, JavaScript li tratta come uguali se, e solo se, hanno la stessa lunghezza e se il carattere a ogni indice è lo stesso.

Gli oggetti sono diversi dai primitivi. Prima di tutto, sono mutabili: i loro valori possono cambiare:

let o = { x: 1 };  // Iniziamo con un oggetto
o.x = 2;           // Lo mutiamo cambiando il valore di una proprietà
o.y = 3;           // Lo mutiamo di nuovo aggiungendo una nuova proprietà

let a = [1,2,3];   // Anche gli array sono mutabili
a[0] = 0;          // Cambiamo il valore di un elemento dell'array
a[3] = 4;          // Aggiungiamo un nuovo elemento dell'array

Gli oggetti non sono confrontati per valore: due oggetti distinti non sono uguali anche se hanno le stesse proprietà e valori. E due array distinti non sono uguali anche se hanno gli stessi elementi nello stesso ordine:

let o = {x: 1}, p = {x: 1};  // Due oggetti con le stesse proprietà
o === p                      // => false: oggetti distinti non sono mai uguali
let a = [], b = [];          // Due array distinti e vuoti
a === b                      // => false: array distinti non sono mai uguali

Gli oggetti sono a volte chiamati tipi di riferimento per distinguerli dai tipi primitivi di JavaScript. Usando questa terminologia, i valori oggetto sono riferimenti, e diciamo che gli oggetti sono confrontati per riferimento: due valori oggetto sono uguali se e solo se si riferiscono allo stesso oggetto sottostante.

let a = [];   // La variabile a si riferisce a un array vuoto.
let b = a;    // Ora b si riferisce allo stesso array.
b[0] = 1;     // Mutiamo l'array a cui si riferisce la variabile b.
a[0]          // => 1: il cambiamento è anche visibile attraverso la variabile a.
a === b       // => true: a e b si riferiscono allo stesso oggetto, quindi sono uguali.

Come si può vedere da questo codice, assegnare un oggetto (o array) a una variabile assegna semplicemente il riferimento: non crea una nuova copia dell'oggetto. Se si vuole creare una nuova copia di un oggetto o array, si devono esplicitamente copiare le proprietà dell'oggetto o gli elementi dell'array. Questo esempio dimostra l'uso di un ciclo for:

let a = ["a","b","c"];              // Un array che si vuole copiare
let b = [];                         // Un array distinto in cui si copierà
for(let i = 0; i < a.length; i++) { // Per ogni indice di a[]
    b[i] = a[i];                    // Si copia un elemento di a in b
}
let c = Array.from(b);              // In ES6, si copiano gli array con Array.from()

Allo stesso modo, se si vogliono confrontare due oggetti o array distinti, si devono confrontare le loro proprietà o elementi. Questo codice definisce una funzione per confrontare due array:

function arrayUguali(a, b) {
    if (a === b) return true;                // Array identici sono uguali
    if (a.length !== b.length) return false; // Array di dimensioni diverse non sono uguali
    for(let i = 0; i < a.length; i++) {      // Scorriamo tutti gli elementi
        if (a[i] !== b[i]) return false;     // Se qualcuno differisce, gli array non sono uguali
    }
    return true;                             // Altrimenti sono uguali
}