Tracci un bug attraverso tre file solo per scoprire che una innocua funzione helper ha rinominato un utente. Non avevi mai toccato l'originale. O almeno così pensavi. In JavaScript, assegnare una variabile a un'altra non significa sempre ciò che ti aspetti. Il linguaggio divide i suoi dati in due strategie di archiviazione, e dimenticare quale si stia utilizzando è il modo in cui le mutazioni silenziose si insinuano nel codice di produzione.

Per prevenire questo problema, devi vedere la differenza tra valori primitivi e oggetti esattamente come fa il motore.

Primitivi: Copie Effettive

Un primitivo è un dato singolo e indivisibile. Non può essere scomposto in valori JavaScript più piccoli. Il linguaggio definisce sette tipi primitivi: String, Number, Boolean, Undefined, Null, Symbol e BigInt.

Poiché i primitivi sono atomici, generalmente risiedono direttamente all'interno del binding della variabile. Quando copi una variabile primitiva in un'altra, il motore duplica i dati effettivi. Ogni variabile riceve il proprio slot indipendente in memoria.

let a = "Alina";
let b = a;
b = "Ali";

console.log(a); // "Alina"
console.log(b); // "Ali"

Qui, a rimane intatto. La riassegnazione di b ha creato un valore completamente nuovo e ha puntato b verso di esso, mentre a conserva ancora la sua stringa originale. Questo comportamento è la "copy by value". Funziona in modo identico per numeri, booleani, symbol e il resto della famiglia dei primitivi. Puoi passarli alle funzioni, riassegnarli o restituirli senza preoccuparti di effetti collaterali sulle variabili correlate.

Oggetti: Indirizzi Condivisi

Gli oggetti sono diversi. Un oggetto è un contenitore composito che raggruppa insieme più pezzi di dati. Questa categoria include oggetti semplici, array, funzioni, date e ogni altro tipo non primitivo. Poiché queste strutture possono diventare grandi e nidificate, JavaScript non memorizza l'intero oggetto all'interno della variabile. Invece, la variabile contiene un riferimento, essenzialmente un indirizzo di memoria che punta ai dati reali memorizzati altrove.

Quando assegni un oggetto a una nuova variabile, il motore copia l'indirizzo, non l'oggetto. Ora due variabili puntano esattamente alla stessa casa.

const user = { name: "Alina" };
const copy = user;
copy.name = "Ali";

console.log(user.name); // "Ali"

Cambiare copy.name ha cambiato anche user.name perché entrambi i nomi risolvono allo stesso oggetto sottostante. Questa è la "copy by reference". La stessa sorpresa si verifica con gli array:

const scores = [82, 91, 74];
const backup = scores;
backup.push(88);

console.log(scores); // [82, 91, 74, 88]

C'è un solo array in memoria. scores e backup sono solo due cartelli che puntano ad esso.

La parola chiave const aggiunge un livello di confusione. Dichiarare un oggetto con const blocca il binding della variabile, in modo da non poterla puntare verso un nuovo indirizzo. Non blocca l'oggetto stesso.

const settings = { theme: "dark" };
settings.theme = "light";        // Works perfectly.
settings = { theme: "dark" };    // TypeError

Gli sviluppatori spesso si aspettano che const garantisca l'immutabilità. Non è così. Impedisce solo la riassegnazione del riferimento. Se vuoi che il contenuto sia sigillato, devi copiare con intenzione.

Dove si nascondono i bug reali

I bug di riferimento raramente si manifestano come ovvie riassegnazioni di variabili. Si nascondono all'interno delle chiamate alle funzioni.

function addTimestamp(record) {
  record.timestamp = Date.now();
  return record;
}

const original = { id: 1 };
addTimestamp(original);

console.log(original.timestamp); // A number now exists here. Oops.

Il parametro record ha ricevuto una copia del riferimento. Qualsiasi mutazione di proprietà all'interno della funzione ha scritto direttamente nell'oggetto del chiamante. La funzione sembrava una semplice trasformazione, eppure ha causato una fuga di stato attraverso i confini dello scope.

Questo pattern è particolarmente doloroso nei framework UI come React, dove gli aggiornamenti dello stato dipendono