$cd~/artigos/primitives-vs-objects-como-valores-javascript
Fundamentals
série 33 Conceitos JS·parte 7 de 38

Primitives vs Objects: Como os Valores do JavaScript Realmente Funcionam

12 minVanderlei José Alamino

Você já se perguntou por que mudar uma variável inesperadamente altera outra? Por que isso acontece?

snippetJavaScript
const original = { name: "Alice" };const copia = original;copia.name = "Bob"; console.log(original.name);  // "Bob" — Espera, o quê?!

A resposta está em como os valores do JavaScript se comportam — não onde são armazenados. Primitivos são imutáveis e agem de forma independente, enquanto objetos são mutáveis e podem ser compartilhados entre variáveis.

Mito vs Realidade: Você pode ter ouvido que "primitivos são armazenados na stack" e "objetos são armazenados no heap," ou que "primitivos são passados por valor" enquanto "objetos são passados por referência." Essas são simplificações tecnicamente incorretas. Neste guia, vamos aprender como o JavaScript realmente funciona.

primitives-vs-objects-como-valores-javascript.meta.jsonjson

// metadados do artigo

{
  "tag": "fundamentos",
  "readTime": "12 min",
  "author": "Vanderlei José Alamino",
  "source": "33jsconcepts.com",
  "series": "33 Conceitos JS",
  "part": {
    "current": 7,
    "total": 38
  }
}
abrir fonte original
primitives-vs-objects-como-valores-javascript.mdmarkdown

Fonte de referência

Conteúdo adaptado a partir do guia original em inglês.

33jsconcepts.com
01

seção

Uma Nota sobre Terminologia

Antes de mergulhar, vamos esclarecer equívocos difundidos que até desenvolvedores experientes confundem.

Mito vs realidade

Mito comumA realidade
"Tipos por valor" vs "tipos por referência"A ECMAScript só define primitivos e objetos
"Primitivos ficam na stack"Específico da implementação — não está na spec
"Objetos ficam no heap"Específico da implementação — não está na spec
"Primitivos são passados por valor"JavaScript usa passagem por compartilhamento para TODOS os valores
"Objetos são passados por referência"Objetos são passados por compartilhamento (você não pode reatribuir o original)

O Que a ECMAScript Realmente Diz

A especificação ECMAScript (o padrão oficial do JavaScript) define exatamente duas categorias de valores:

Termo ECMAScriptO que inclui
Valores primitivosstring, number, bigint, boolean, undefined, null, symbol
ObjetosTodo o resto (objetos literais, arrays, funções, dates, maps, sets, etc.)

É isso. A spec nunca menciona "tipos por valor", "tipos por referência", "stack" ou "heap". São detalhes de implementação que variam conforme o engine JavaScript.

A distinção real: mutabilidade

A diferença fundamental entre primitivos e objetos é a mutabilidade:

  • Primitivos são imutáveis — você não pode alterar um valor primitivo, apenas substituí-lo
  • Objetos são mutáveis — você PODE alterar o conteúdo de um objeto

Essa distinção explica TODAS as diferenças de comportamento que você vai encontrar.

02

seção

Como Primitivos e Objetos Se Comportam

Primitivos: imutáveis e independentes

Os 7 tipos primitivos se comportam como se cada variável tivesse sua própria cópia independente:

TipoExemploComportamento-chave
string"hello"Imutável — métodos retornam strings NOVAS
number42Imutável — operações criam números NOVOS
bigint9007199254740993nImutável — operações criam BigInts NOVOS
booleantrueImutável
undefinedundefinedImutável
nullnullImutável
symbolSymbol("id")Imutável E tem identidade

Características-chave:

  • Imutáveis — você não altera o valor, apenas substitui
  • Independentes — cópias não afetam uma à outra
  • Comparados por valor — mesmo valor = igual (exceto Symbols)

Por que a imutabilidade importa: quando você escreve str.toUpperCase(), obtém uma string NOVA. A str original permanece inalterada. Isso vale para TODOS os métodos de string — eles nunca mutam a string original.

snippetJavaScript
let greeting = "hello";let shout = greeting.toUpperCase(); console.log(greeting);  // "hello" — inalterado!console.log(shout);     // "HELLO" — string nova

Objetos: mutáveis e compartilhados

Tudo que não é primitivo é um objeto:

TipoExemploComportamento-chave
Object{ name: "Alice" }Mutável — propriedades podem mudar
Array[1, 2, 3]Mutável — elementos podem mudar
Functionfunction() {}Mutável (tem propriedades)
Datenew Date()Mutável
Mapnew Map()Mutável
Setnew Set()Mutável

Características-chave:

  • Mutáveis — você PODE alterar o conteúdo
  • Compartilhados por padrão — atribuição copia a referência, não o objeto
  • Comparados por identidade — mesmo objeto = igual (não mesmo conteúdo!)
03

seção

A Analogia da Chave da Casa

Pense em objetos como casas e variáveis como chaves dessas casas:

Primitivos (como escrever um bilhete): você escreve "42" num post-it e dá uma cópia ao amigo. Cada um tem seu bilhete independente. Se ele mudar o dele para "100", o seu ainda diz "42".

Objetos (como compartilhar chaves de casa): em vez de dar a casa em si, você dá uma cópia da chave. Os dois têm chaves para a MESMA casa. Se ele reorganizar os móveis, você também verá — porque é a mesma casa!

A ideia central: não é sobre onde a chave está guardada, é sobre para onde ela aponta.

04

seção

Call by Sharing: Como o JavaScript Passa Argumentos

É aqui que a maioria dos tutoriais erra. O JavaScript não usa "pass by value" NEM "pass by reference." Ele usa uma terceira estratégia chamada call by sharing (também conhecida como "call by object sharing").

O call by sharing foi descrito pela primeira vez por Barbara Liskov para a linguagem de programação CLU em 1974. JavaScript, Python, Ruby e Java usam todas essa estratégia de avaliação.

O Que é Call by Sharing?

Quando você passa um argumento para uma função, o JavaScript:

  1. Cria uma cópia da referência (a "chave" do objeto)
  1. O parâmetro da função recebe essa referência copiada
  1. Tanto a variável original QUANTO o parâmetro apontam para o MESMO objeto

A Regra de Ouro

OperaçãoAfeta o original?
Mutar propriedades (obj.name = "Bob")✅ Sim — mesmo objeto
Reatribuir o parâmetro (obj = newValue)❌ Não — só reassocia localmente

Mutação funciona

Quando você modifica um objeto através de um parâmetro de função, o objeto original é afetado:

snippetJavaScript
function rename(person) {  person.name = "Bob";  // Muta o objeto ORIGINAL} const user = { name: "Alice" };rename(user); console.log(user.name);  // "Bob" — mudou!

O que acontece na memória:

Reatribuição não funciona

Se você reatribui o parâmetro a um novo objeto, só muda a variável local:

snippetJavaScript
function replace(person) {  person = { name: "Charlie" };  // Cria referência local NOVA} const user = { name: "Alice" };replace(user); console.log(user.name);  // "Alice" — inalterado!

O que acontece na memória:

Por que isso importa: Se o JavaScript usasse verdadeiro "pass by reference" (como referências do C++), reatribuir o parâmetro MUDARIA o original. Não acontece no JavaScript — e é assim que você sabe que é "call by sharing," não "pass by reference."

Isso Também se Aplica a Primitivos!

Aqui está a parte que confunde a cabeça: primitivos também são passados por sharing. Você simplesmente não consegue observar isso porque primitivos são imutáveis — não há como mutá-los através do parâmetro.

snippetJavaScript
function double(num) {  num = num * 2;    // Reatribui a variável LOCAL  return num;} let x = 10;let result = double(x); console.log(x);       // 10 — inalterado (reatribuição não afeta o original)console.log(result);  // 20 — valor retornado

A mesma regra de "reatribuição não funciona" vale para primitivos. Só que com primitivos não há mutação para tentar mesmo!

05

seção

Comportamento de Cópia: A Diferença Crítica

É aqui que os bugs adoram se esconder.

Copiando primitivos: cópias independentes

Quando você copia um primitivo, eles se comportam como valores completamente independentes:

snippetJavaScript
let a = 10;let b = a;      // b recebe cópia independente b = 20;         // mudar b NÃO afeta a console.log(a); // 10 (inalterado!)console.log(b); // 20

Copiando objetos: referências compartilhadas

Quando você copia uma variável de objeto, copia a referência. Ambas apontam para o MESMO objeto:

snippetJavaScript
let obj1 = { name: "Alice" };let obj2 = obj1;       // obj2 recebe cópia da REFERÊNCIA obj2.name = "Bob";     // modifica o MESMO objeto! console.log(obj1.name); // "Bob" (mudou!)console.log(obj2.name); // "Bob"

A armadilha dos arrays

Arrays também são objetos, então se comportam da mesma forma:

snippetJavaScript
let arr1 = [1, 2, 3];let arr2 = arr1;        // arr2 aponta para o MESMO array arr2.push(4);           // modifica o array compartilhado console.log(arr1);      // [1, 2, 3, 4] — Espera, o quê?!console.log(arr2);      // [1, 2, 3, 4]

Isso pega TODO MUNDO no começo! Quando você escreve let arr2 = arr1, NÃO está criando um array novo. Está criando uma segunda variável que aponta para o mesmo array. Qualquer mudança por qualquer uma das variáveis afeta as duas.

06

seção

Comportamento de Comparação

Primitivos: comparados por valor

Dois primitivos são iguais se têm o mesmo valor:

snippetJavaScript
let a = "hello";let b = "hello";console.log(a === b);   // true — mesmo valor let x = 42;let y = 42;console.log(x === y);   // true — mesmo valor

Objetos: comparados por identidade

Dois objetos são iguais apenas se são o MESMO objeto (mesma referência):

snippetJavaScript
let obj1 = { name: "Alice" };let obj2 = { name: "Alice" };console.log(obj1 === obj2);  // false — objetos diferentes! let obj3 = obj1;console.log(obj1 === obj3);  // true — mesma referência

A armadilha do objeto/array vazio

snippetJavaScript
console.log({} === {});     // false — dois objetos vazios diferentesconsole.log([] === []);     // false — dois arrays vazios diferentesconsole.log([1,2] === [1,2]); // false — dois arrays diferentes

Como comparar objetos/arrays por conteúdo:

snippetJavaScript
// Abordagem simples (mas limitada)JSON.stringify(obj1) === JSON.stringify(obj2) // Para arrays de primitivosarr1.length === arr2.length && arr1.every((v, i) => v === arr2[i]) // Para casos complexos, use biblioteca como Lodash_.isEqual(obj1, obj2)

Cuidado com JSON.stringify: a ordem das propriedades importa! {a:1, b:2} e {b:2, a:1} produzem strings diferentes. Também falha com undefined, funções, Symbols, referências circulares, NaN e Infinity.

Symbols: a exceção

Symbols são primitivos, mas têm identidade — dois symbols com a mesma descrição NÃO são iguais:

snippetJavaScript
const sym1 = Symbol("id");const sym2 = Symbol("id"); console.log(sym1 === sym2);  // false — symbols diferentes!console.log(sym1 === sym1);  // true — mesmo symbol
07

seção

Mutação vs Reatribuição

Entender essa distinção é crucial para evitar bugs.

Mutação: alterar o conteúdo

Mutação modifica o objeto existente no lugar:

snippetJavaScript
const arr = [1, 2, 3]; // Todas são MUTAÇÕES:arr.push(4);         // [1, 2, 3, 4]arr[0] = 99;         // [99, 2, 3, 4]arr.pop();           // [99, 2, 3]arr.sort();          // modifica no lugar const obj = { name: "Alice" }; // Todas são MUTAÇÕES:obj.name = "Bob";        // altera propriedadeobj.age = 25;            // adiciona propriedadedelete obj.age;          // remove propriedade

Reatribuição: apontar para um valor novo

Reatribuição faz a variável apontar para algo completamente diferente:

snippetJavaScript
let arr = [1, 2, 3];arr = [4, 5, 6];      // REATRIBUIÇÃO — array novo let obj = { name: "Alice" };obj = { name: "Bob" }; // REATRIBUIÇÃO — objeto novo

A armadilha do const

const impede reatribuição, mas NÃO impede mutação:

snippetJavaScript
const arr = [1, 2, 3]; // ✅ Mutações são PERMITIDAS:arr.push(4);           // funciona!arr[0] = 99;           // funciona! // ❌ Reatribuição é BLOQUEADA:arr = [4, 5, 6];       // TypeError: Assignment to constant variable const obj = { name: "Alice" }; // ✅ Mutações são PERMITIDAS:obj.name = "Bob";      // funciona!obj.age = 25;          // funciona! // ❌ Reatribuição é BLOQUEADA:obj = { name: "Eve" }; // TypeError: Assignment to constant variable

Equívoco comum: muitos desenvolvedores acham que const cria uma variável "imutável". Não cria! Só impede reatribuição. O conteúdo de objetos e arrays declarados com const ainda pode ser alterado.

08

seção

Imutabilidade de Verdade com `Object.freeze()`

Se você precisa de um objeto verdadeiramente imutável, use Object.freeze():

snippetJavaScript
const user = Object.freeze({ name: "Alice", age: 25 }); user.name = "Bob";      // Falha silenciosamente (ou lança erro em strict mode)user.email = "a@b.com"; // Não pode adicionar propriedadesdelete user.age;        // Não pode remover propriedades console.log(user);      // { name: "Alice", age: 25 } — inalterado!

Object.freeze() é superficial! Só congela o nível superior. Objetos aninhados ainda podem ser modificados:

snippetJavaScript
const user = Object.freeze({  name: "Alice",  address: { city: "NYC" }}); user.name = "Bob";           // Bloqueadouser.address.city = "LA";    // Funciona! Objeto aninhado não foi congelado console.log(user.address.city); // "LA"

Para congelamento profundo, você precisa de uma função recursiva ou usar structuredClone() para criar uma cópia profunda primeiro.

09

seção

Cópia Rasa vs Cópia Profunda

Quando você precisa de uma cópia verdadeiramente independente de um objeto, você tem duas opções.

Cópia Rasa: Um Nível de Profundidade

Uma cópia superficial cria um objeto novo com cópias das propriedades de nível superior. Mas objetos aninhados ainda são compartilhados!

snippetJavaScript
const original = {   name: "Alice",  address: { city: "NYC" }}; // Métodos de cópia superficial:const copy1 = { ...original };           // Spread operatorconst copy2 = Object.assign({}, original); // Object.assign // Mudanças de nível superior são independentes:copy1.name = "Bob";console.log(original.name);  // "Alice" ✅ // Mas objetos aninhados são COMPARTILHADOS:copy1.address.city = "LA";console.log(original.address.city);  // "LA" 😱

Cópia Profunda: Todos os Níveis

Uma cópia profunda cria cópias completamente independentes em todos os níveis.

snippetJavaScript
const original = {   name: "Alice",  scores: [95, 87, 92],  address: { city: "NYC" }}; // structuredClone() — a forma moderna (ES2022+)const deep = structuredClone(original); // Agora tudo é independente:deep.address.city = "LA";console.log(original.address.city);  // "NYC" ✅ deep.scores.push(100);console.log(original.scores);  // [95, 87, 92] ✅

Qual usar:

  • structuredClone() — Documentado pela MDN, esta API está disponível em todos os principais navegadores desde 2022 e é a abordagem recomendada para a maioria dos casos
  • JSON.parse(JSON.stringify()) — Apenas para objetos simples (perde funções, Dates, undefined)
  • Lodash _.cloneDeep() — Quando você precisa de máxima compatibilidade
10

seção

Como os Motores Realmente Armazenam Valores

Por que esta seção existe: Muitos tutoriais ensinam que "primitivos vão para a stack, objetos vão para o heap." Essa é uma simplificação que frequentemente está errada. Veja o que realmente acontece.

A Especificação ECMAScript Não Define Armazenamento

A especificação ECMAScript define comportamento, não implementação. Nunca menciona "stack" ou "heap". Engines JavaScript diferentes podem armazenar valores como quiserem, desde que o comportamento siga a spec.

Como o V8 realmente funciona

O V8 (Chrome, Node.js, Deno) usa uma técnica chamada pointer tagging para representar valores com eficiência. Segundo o blog do time V8, essa otimização é crítica para performance — permite distinguir inteiros pequenos de ponteiros para o heap sem buscas extras na memória.

Smis (Small Integers): os únicos valores "diretos"

Os ÚNICOS valores que o V8 armazena "diretamente" (não no heap) são Smis — Small Integers na faixa aproximada de -2³¹ a 2³¹-1 (cerca de -2 bilhões a 2 bilhões).

Todo o resto vive no heap

Isso inclui valores que você pode achar "simples":

Tipo de valorOnde é armazenadoPor quê
Inteiros pequenos (-2³¹ a 2³¹-1)Diretamente (como Smi)Tamanho fixo, cabe no ponteiro
Números grandesHeap (HeapNumber)Precisa de float de 64 bits
StringsHeapTamanho dinâmico
BigIntsHeapPrecisão arbitrária
Objetos, ArraysHeapEstruturas complexas

O grande equívoco: strings NÃO são valores de tamanho fixo na stack. Uma string "hello" e uma com um milhão de caracteres são armazenadas no heap. A variável só guarda um ponteiro para essa localização.

Internamento de strings

O V8 otimiza strings idênticas potencialmente compartilhando memória (string interning). Duas variáveis com o valor "hello" podem apontar para o mesmo local na memória internamente. Mas isso é otimização — strings ainda se comportam como valores independentes porque são imutáveis.

Por que o modelo stack/heap é ensinado

O modelo simplificado stack/heap é útil para entender diferenças de comportamento:

  • Coisas que "se comportam como valores de stack" = agem de forma independente
  • Coisas que "se comportam como valores de heap" = podem ser compartilhadas

Só saiba que é um modelo mental para comportamento, não como o JavaScript realmente funciona internamente.

Quer ir mais fundo? Confira nosso guia sobre Motores JavaScript para mais sobre internos do V8, compilação JIT e otimização.

11

seção

Bugs e Armadilhas Comuns

1. Mutação acidental de objeto/array

snippetJavaScript
// BUG: Modificando parâmetro de funçãofunction processUsers(users) {  users.push({ name: "Novo Usuário" });  // Muta o original!  return users;} const myUsers = [{ name: "Alice" }];processUsers(myUsers);console.log(myUsers);  // [{ name: "Alice" }, { name: "Novo Usuário" }] // CORREÇÃO: Crie uma cópia primeirofunction processUsers(users) {  const copy = [...users];  copy.push({ name: "Novo Usuário" });  return copy;}

2. Métodos de array que mutam

snippetJavaScript
// Estes MUTAM o array original:arr.push()      arr.pop()arr.shift()     arr.unshift()arr.splice()    arr.sort()arr.reverse()   arr.fill() // Estes RETORNAM array novo (seguros):arr.map()       arr.filter()arr.slice()     arr.concat()arr.flat()      arr.flatMap()arr.toSorted()  arr.toReversed()  // ES2023arr.toSpliced() // ES2023 // ARMADILHA: sort() muta!const nums = [3, 1, 2];const sorted = nums.sort();  // nums agora é [1, 2, 3]! // CORREÇÃO: Copie primeiro, ou use toSorted()const sorted = [...nums].sort();const sorted = nums.toSorted();  // ES2023

3. Comparar objetos/arrays

snippetJavaScript
// BUG: Isso NUNCA vai funcionarif (user1 === user2) { }      // Compara identidadeif (arr1 === arr2) { }        // Compara identidade // Até estes falham:[] === []                      // false{} === {}                      // false[1, 2] === [1, 2]              // false // CORREÇÃO: Compare conteúdoJSON.stringify(a) === JSON.stringify(b)  // Simples mas limitado // Ou use função/biblioteca de igualdade profunda

4. Cópia superficial com objetos aninhados

snippetJavaScript
// BUG: Cópia superficial não clona objetos aninhadosconst user = {  name: "Alice",  settings: { theme: "dark" }}; const copy = { ...user };copy.settings.theme = "light"; console.log(user.settings.theme);  // "light" — Original mudou! // CORREÇÃO: Use cópia profundaconst copy = structuredClone(user);

5. Esquecer que arrays são objetos

snippetJavaScript
// BUG: Achar que tem dois arraysconst original = [1, 2, 3];const backup = original;  // NÃO é backup! original.push(4);console.log(backup);  // [1, 2, 3, 4] — "backup" mudou! // CORREÇÃO: Copie o array de verdadeconst backup = [...original];const backup = original.slice();

6. Esperar que reatribuição afete o original

snippetJavaScript
// BUG: Achar que reatribuição passa adiantefunction clearArray(arr) {  arr = [];  // Só reatribui variável local!} const myArr = [1, 2, 3];clearArray(myArr);console.log(myArr);  // [1, 2, 3] — inalterado! // CORREÇÃO: Mute em vez de reatribuirfunction clearArray(arr) {  arr.length = 0;  // Muta o original}
12

seção

Boas Práticas

Diretrizes para trabalhar com objetos:

  1. Trate objetos como imutáveis quando possível
snippetJavaScript
// Em vez de mutar:user.name = "Bob"; // Crie objeto novo:const updatedUser = { ...user, name: "Bob" };
  1. Use const por padrão — previne reatribuição acidental
  1. Saiba quais métodos mutam
  • Mutam: push, pop, sort, reverse, splice
  • Não mutam: map, filter, slice, concat, toSorted
  1. Use structuredClone() para cópias profundas
snippetJavaScript
const clone = structuredClone(original);
  1. Clone parâmetros de função se precisar modificá-los
snippetJavaScript
function processData(data) {  const copy = structuredClone(data);  // Agora seguro para modificar copy}
  1. Seja explícito sobre intenção — comente quando mutar de propósito
13

seção

Teste Seu Conhecimento

$ summary --quick

Resumo rápido

  • Primitivos vs Objetos — os termos da ECMAScript (não "tipos por valor" vs "tipos por referência")
  • A diferença real é mutabilidade — primitivos são imutáveis, objetos são mutáveis
  • Call by sharing — o JavaScript passa TODOS os valores como cópias de referências; mutação funciona, reatribuição não
  • Identidade de objetos — objetos são comparados por identidade, não por conteúdo ({} === {} é false)
  • const impede reatribuição, não mutação — use Object.freeze() para imutabilidade verdadeira
  • Cópia superficial compartilha objetos aninhados — use structuredClone() para cópias profundas
  • Conheça seus métodos de array — push/pop/sort mutam; map/filter/slice não
  • O modelo stack/heap é simplificação — útil para entender comportamento, não tecnicamente preciso
  • No V8, só Smis são armazenados diretamente — strings, BigInts e objetos vivem no heap
  • Symbols têm identidade — dois Symbol("id") são diferentes, ao contrário de outros primitivos

Créditos da série

Este artigo faz parte de uma série de estudos inspirada no projeto 33 Conceitos JS, criado por Leonardo Maldonado. Todo o conteúdo original está disponível em 33jsconcepts.com.