Mutabilità e Immutabilità in Javascript
- 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
}