O contexto
Eu estava acostumado a tratar variáveis como simples etiquetas para valores. Você define um nome, atribui um valor e pronto, o JavaScript cuida do resto. Mas quando comecei a lidar com objetos complexos e arrays grandes, o comportamento de "copiar" uma variável começou a me confundir. O que parecia uma cópia às vezes era apenas um espelho. Foi aí que percebi: eu não sabia onde meus dados estavam morando na RAM.
A ilusão das variáveis
No JavaScript, a gente não lida com endereços de memória manualmente como no C, mas a lógica por trás é a mesma. O problema surge quando você tenta duplicar uma informação.
let a = 10;
let b = a;
b = 20;
console.log(a); // 10 - Tudo certo aqui.
let obj1 = { nome: "Gabriel" };
let obj2 = obj1;
obj2.nome = "Gomes";
console.log(obj1.nome); // "Gomes" - Por que diabos o obj1 mudou?
O erro não estava no código, estava na minha imagem mental de como o computador processa isso. Isso me lembrou muito de quando comecei a estudar o básico do backend, onde a estrutura dos dados começa a importar mais do que a aparência na tela.
O que descobri: Stack vs Heap
A memória que o JavaScript usa é dividida em dois grandes espaços com regras bem diferentes.
O Stack (Pilha) é onde moram os tipos primitivos (Number, String, Boolean, null, undefined). É um espaço organizado, rápido e de tamanho fixo. Quando você faz let b = a, o JavaScript cria uma cópia física do valor no Stack. São dois pedaços de memória independentes.
O Heap (Amontoado) é onde moram os objetos, arrays e funções. Diferente do Stack, o Heap é um espaço dinâmico e "bagunçado", feito para guardar coisas que podem crescer. O JavaScript não guarda o objeto direto na variável. Ele guarda o objeto no Heap e coloca apenas o "endereço" (a referência) desse objeto no Stack.
Quando eu fiz let obj2 = obj1, eu não copiei o objeto. Eu copiei o endereço. Os dois agora apontam para a mesma porta no Heap. Se um entra e muda o sofá de lugar, o outro vai ver a mudança quando entrar. Esse tipo de detalhe é o que diferencia quem apenas segue tutoriais de quem entende o que acontece por baixo.
O que isso muda
Entender essa separação remove o "mistério" de bugs estranhos com objetos. Agora eu sei que se eu quero uma cópia real, preciso de um spread operator {...obj} ou um structuredClone(), porque preciso criar um novo espaço no Heap.
Isso também me deu uma perspectiva melhor sobre performance. O Stack é barato. O Heap é caro. Cada vez que você cria um objeto dentro de um loop sem necessidade, está pedindo mais espaço pro Heap e dando trabalho pro Garbage Collector (o zelador da memória) limpar a bagunça depois.
O que fica
O JavaScript abstrai muita coisa, mas a RAM é finita e as regras de arquitetura ainda se aplicam. Saber a diferença entre valor e referência não é "excesso de teoria", é a base para não criar race conditions bizarras ou memory leaks que só aparecem em produção.
Referências
- MDN: Memory Management — O guia definitivo sobre como o JS limpa a casa.
- V8 Blog — Para entender como o motor do Chrome otimiza essa brincadeira.