No ecossistema JavaScript, compreender como os dados são armazenados e manipulados na memória é fundamental para evitar bugs de mutabilidade indesejada. As variáveis podem ser categorizadas em dois grandes grupos baseados em como armazenam suas informações: Tipos de Valor (primitivos) e Tipos de Referência (objetos).
- Definições Fundamentais
Tipos de Valor: Também conhecidos como primitivos, esses tipos armazenam o dado real diretamente na posição de memória da variável. Quando você atribui uma variável de valor a outra, o JavaScript cria uma cópia física e independente do conteúdo.
- Number
- String
- Boolean
- null e undefined
- Symbol e BigInt
Tipos de Referência: Estes tipos não armazenam o valor diretamente. Em vez disso, a variável guarda um endereço de memória (um ponteiro) que indica onde o objeto real está alocado na "Heap". Ao atribuir um tipo de referência a uma nova variável, você está apenas copiando o endereço, não o objeto em si.
- Object
- Array
- Function
- Comportamento em Atribuições
A principal diferença prática manifesta-se quando tentamos copiar dados. Veja como os tipos de valor se comportam:
// Exemplo com Tipos de Valor
let notaMatematica = 85;
let notaPortugues = notaMatematica; // Cópia do valor real
notaPortugues = 95; // Altera apenas notaPortugues
console.log(notaMatematica); // Saída: 85
console.log(notaPortugues); // Saída: 95
console.log(notaMatematica === notaPortugues); // false
No exemplo acima, notaMatematica permanece intacta porque a atribuição criou uma entidade completamente nova na memória.
Agora, observe o comportamento com tipos de referência:
// Exemplo com Tipos de Referência
const perfilUsuario = { nome: "Carlos", idade: 30 };
const editorPerfil = perfilUsuario; // Cópia apenas da referência (endereço)
editorPerfil.idade = 31;
console.log(perfilUsuario.idade); // Saída: 31 (O original foi afetado!)
console.log(editorPerfil === perfilUsuario); // true (Apontam para o mesmo local)
// Comparação entre objetos diferentes com conteúdo idêntico
const outroPerfil = { nome: "Carlos", idade: 31 };
console.log(perfilUsuario === outroPerfil); // false (Referências de memória distintas)
Neste caso, perfilUsuario e editorPerfil são como dois controles remotos para a mesma televisão. Mudar o canal em um afetará o que é visto através do outro.
- Passagem de Parâmetros em Funções
Quando passamos variáveis para funções, o comportamento segue a mesma lógica de atribuição mencionada antreiormente.
Passagem por Valor
Se passarmos um primitivo, qualquer alteração dentro da função é local ao escopo da função.
function atualizarSaldo(valor) {
valor = valor + 100;
return valor;
}
let saldoAtual = 500;
atualizarSaldo(saldoAtual);
console.log(saldoAtual); // Saída: 500 (O valor externo não muda)
Passagem por Referência
Se passarmos um objeto ou array, a função recebe o endereço de memória, permitindo que o estado interno do objeto original seja modificado.
function adicionarItem(carrinho) {
carrinho.push("Teclado Mecânico");
}
const meuCarrinho = ["Mouse"];
adicionarItem(meuCarrinho);
console.log(meuCarrinho); // Saída: ["Mouse", "Teclado Mecânico"]
- Estruturas Complexas
É comum termos objetos que contêm outros objetos. A referência funciona em cadeia, mantendo a ligação com os dados originais enquanto o caminho de memória for preservado.
const motor = { cavalos: 150, tipo: "Flex" };
const carro = {
modelo: "Sedan",
fabricante: "Honda",
especificacoes: motor
};
const oficina = carro;
oficina.especificacoes.cavalos = 200;
console.log(motor.cavalos); // Saída: 200 (Alterado via referência encadeada)