Objetos: estruturando dados do mundo real

Objetos: estruturando dados do mundo real

Atribuir um objeto a outra variável não copia coisa nenhuma: as duas passam a apontar para o mesmo lugar, e mexer numa altera a outra. Em volta desse susto ficam as duas notações de acesso, o this que fala do próprio objeto, o optional chaining que evita o TypeError e o Object.entries.
Javascript

11 min de leitura

Arrays são ótimos para listas. Mas como representar uma pessoa, um produto, um pedido — entidades com várias características diferentes? Para isso existem os objetos.

Um objeto é uma coleção de propriedades, onde cada propriedade tem um nome (chave) e um valor. É a estrutura de dados mais fundamental do JavaScript — tanto que a linguagem inteira foi construída em cima dela. Arrays são objetos. Funções são objetos. O próprio document do navegador é um objeto.

Entender objetos profundamente é entender o JavaScript.

Criando objetos

const pessoa = {
  nome: "Ana Paula",
  idade: 28,
  profissao: "Desenvolvedora",
  ativa: true,
};

A sintaxe usa chaves {}, com pares de chave: valor separados por vírgula. Isso se chama objeto literal — a forma mais comum de criar objetos.

Acessando propriedades

Há duas formas de acessar valores:

const pessoa = {
  nome: "Ana Paula",
  idade: 28,
  profissao: "Desenvolvedora",
};

// Notação de ponto (use sempre que possível)
console.log(pessoa.nome);      // Ana Paula
console.log(pessoa.idade);     // 28

// Notação de colchetes (use quando a chave é dinâmica)
const campo = "profissao";
console.log(pessoa[campo]);    // Desenvolvedora
console.log(pessoa["nome"]);   // Ana Paula

A notação de colchetes é essencial quando você não sabe o nome da propriedade antecipadamente — quando ela vem de uma variável, por exemplo.

Modificando e adicionando propriedades

const produto = {
  nome: "Notebook",
  preco: 3500,
};

// Modificar propriedade existente
produto.preco = 3200;
console.log(produto.preco); // 3200

// Adicionar nova propriedade
produto.estoque = 15;
console.log(produto.estoque); // 15

// Remover propriedade
delete produto.estoque;
console.log(produto.estoque); // undefined

Propriedades com funções — métodos

Quando uma propriedade de um objeto é uma função, ela é chamada de método:

const calculadora = {
  somar: function(a, b) {
    return a + b;
  },
  subtrair: function(a, b) {
    return a - b;
  },
  // Sintaxe moderna de método (shorthand):
  multiplicar(a, b) {
    return a * b;
  },
};

console.log(calculadora.somar(5, 3));       // 8
console.log(calculadora.subtrair(10, 4));   // 6
console.log(calculadora.multiplicar(3, 7)); // 21

A palavra-chave this

Dentro de um método, this se refere ao próprio objeto. É como o objeto falando de si mesmo:

const conta = {
  titular: "Carlos",
  saldo: 1000,

  depositar(valor) {
    this.saldo += valor;
    console.log(`Depósito de R$ ${valor}. Novo saldo: R$ ${this.saldo}`);
  },

  sacar(valor) {
    if (valor > this.saldo) {
      console.log("Saldo insuficiente.");
      return;
    }
    this.saldo -= valor;
    console.log(`Saque de R$ ${valor}. Novo saldo: R$ ${this.saldo}`);
  },

  verSaldo() {
    console.log(`${this.titular}, seu saldo é R$ ${this.saldo}`);
  },
};

conta.verSaldo();    // Carlos, seu saldo é R$ 1000
conta.depositar(500); // Depósito de R$ 500. Novo saldo: R$ 1500
conta.sacar(200);     // Saque de R$ 200. Novo saldo: R$ 1300
conta.sacar(2000);    // Saldo insuficiente.

Atenção: this dentro de arrow functions não se refere ao objeto — por isso usamos a sintaxe de função normal nos métodos.

Objetos aninhados

Propriedades de um objeto podem ser outros objetos:

const usuario = {
  nome: "Fernanda",
  idade: 31,
  endereco: {
    rua: "Av. Paulista",
    numero: 1000,
    cidade: "São Paulo",
    estado: "SP",
  },
  contato: {
    email: "fernanda@email.com",
    telefone: "11 99999-0000",
  },
};

console.log(usuario.endereco.cidade);    // São Paulo
console.log(usuario.contato.email);      // fernanda@email.com
console.log(usuario.endereco.numero);    // 1000

Arrays de objetos — o padrão mais comum

Na prática, você vai trabalhar o tempo todo com listas de objetos:

const produtos = [
  { id: 1, nome: "Notebook", preco: 3500, categoria: "eletrônicos" },
  { id: 2, nome: "Mouse", preco: 80, categoria: "eletrônicos" },
  { id: 3, nome: "Cadeira", preco: 900, categoria: "móveis" },
  { id: 4, nome: "Mesa", preco: 700, categoria: "móveis" },
];

// Filtrar por categoria
const eletronicos = produtos.filter(p => p.categoria === "eletrônicos");
console.log(eletronicos);

// Encontrar produto por id
const produto = produtos.find(p => p.id === 3);
console.log(produto.nome); // Cadeira

// Listar nomes de todos os produtos
const nomes = produtos.map(p => p.nome);
console.log(nomes); // ["Notebook", "Mouse", "Cadeira", "Mesa"]

// Total do valor em estoque
const total = produtos.reduce((acc, p) => acc + p.preco, 0);
console.log(`Total: R$ ${total}`); // Total: R$ 5180

Viu como map, filter e reduce do artigo Arrays: criando e manipulando listas se encaixam perfeitamente aqui?

Verificando se uma propriedade existe

const usuario = { nome: "João", idade: 25 };

// Operador in
console.log("nome" in usuario);    // true
console.log("email" in usuario);   // false

// Optional chaining — evita erros ao acessar propriedades aninhadas
const config = {
  tema: {
    cor: "azul",
  },
};

console.log(config.tema?.cor);       // "azul"
console.log(config.idioma?.codigo);  // undefined (sem erro!)
// Sem o ?. isso causaria: TypeError: Cannot read properties of undefined

O optional chaining (?.) é um dos recursos mais úteis do JavaScript moderno. Use-o sempre que acessar propriedades que podem não existir.

Iterando sobre objetos

const pessoa = {
  nome: "Ricardo",
  idade: 35,
  cidade: "Curitiba",
};

// for...in — percorre as chaves do objeto
for (const chave in pessoa) {
  console.log(`${chave}: ${pessoa[chave]}`);
}
// nome: Ricardo
// idade: 35
// cidade: Curitiba

// Object.keys — array com as chaves
console.log(Object.keys(pessoa));
// ["nome", "idade", "cidade"]

// Object.values — array com os valores
console.log(Object.values(pessoa));
// ["Ricardo", 35, "Curitiba"]

// Object.entries — array de pares [chave, valor]
console.log(Object.entries(pessoa));
// [["nome", "Ricardo"], ["idade", 35], ["cidade", "Curitiba"]]

Object.entries() é especialmente poderoso porque você pode encadear métodos de array sobre ele:

const precos = { notebook: 3500, mouse: 80, teclado: 200 };

// Filtrar itens acima de R$ 100 e exibir formatado
Object.entries(precos)
  .filter(([, valor]) => valor > 100)
  .forEach(([produto, valor]) => {
    console.log(`${produto}: R$ ${valor}`);
  });

// notebook: R$ 3500
// teclado: R$ 200

Copiando objetos

Objetos são passados por referência — isso é uma fonte clássica de bugs para iniciantes:

const original = { nome: "Ana", idade: 25 };

// ❌ Isso NÃO cria uma cópia — ambas apontam para o mesmo objeto
const copia = original;
copia.nome = "Maria";

console.log(original.nome); // "Maria" ← o original foi alterado!

Para copiar corretamente, use o spread operator:

const original = { nome: "Ana", idade: 25 };

// ✅ Cria uma cópia independente (shallow copy)
const copia = { ...original };
copia.nome = "Maria";

console.log(original.nome); // "Ana" ← intacto
console.log(copia.nome);    // "Maria"

Vale reter que o spread copia apenas o primeiro nível: objetos aninhados continuam compartilhados entre a cópia e o original.

Tarefa para você

Modele um sistema simples de biblioteca:

// 1. Crie um array com pelo menos 4 livros
//    Cada livro deve ter: titulo, autor, ano, disponivel (boolean), paginas

// 2. Filtre apenas os livros disponíveis

// 3. Encontre todos os livros publicados após 2000

// 4. Exiba o título e autor de cada livro no formato:
//    "Dom Casmurro — Machado de Assis"

// 5. Calcule o total de páginas de todos os livros

// Dica: combine map, filter e reduce com seu array de objetos
Ver solução — a biblioteca completa, dos livros ao total de páginas
// ---------------------------------------------------------------
// 1 — o acervo
// ---------------------------------------------------------------
const livros = [
  { titulo: "Dom Casmurro", autor: "Machado de Assis", ano: 1899, disponivel: true, paginas: 256 },
  { titulo: "Torto Arado", autor: "Itamar Vieira Junior", ano: 2019, disponivel: true, paginas: 264 },
  { titulo: "A Hora da Estrela", autor: "Clarice Lispector", ano: 1977, disponivel: false, paginas: 96 },
  { titulo: "O Avesso da Pele", autor: "Jeferson Tenório", ano: 2020, disponivel: true, paginas: 176 },
  { titulo: "Vidas Secas", autor: "Graciliano Ramos", ano: 1938, disponivel: false, paginas: 176 },
];

// ---------------------------------------------------------------
// 2 — só os disponíveis
// ---------------------------------------------------------------
// `livro.disponivel` já é boolean: comparar com === true é redundante.
const disponiveis = livros.filter((livro) => livro.disponivel);
console.log(`${disponiveis.length} de ${livros.length} disponíveis`);

// ---------------------------------------------------------------
// 3 — publicados depois de 2000
// ---------------------------------------------------------------
const recentes = livros.filter((livro) => livro.ano > 2000);
console.log(recentes.map((l) => `${l.titulo} (${l.ano})`));
// [ 'Torto Arado (2019)', 'O Avesso da Pele (2020)' ]

// ---------------------------------------------------------------
// 4 — "Título — Autor"
// ---------------------------------------------------------------
// O travessão é U+2014, não hífen. Copiado errado, a lista fica com
// "Dom Casmurro - Machado" e ninguém entende por que ficou feio.
for (const { titulo, autor } of livros) {
  console.log(`${titulo} — ${autor}`);
}

// ---------------------------------------------------------------
// 5 — total de páginas
// ---------------------------------------------------------------
const totalPaginas = livros.reduce((soma, livro) => soma + livro.paginas, 0);
console.log(`Total: ${totalPaginas} páginas`); // Total: 968 páginas

// ---------------------------------------------------------------
// Os três juntos: relatório do acervo
// ---------------------------------------------------------------
function relatorio(acervo) {
  const emprestados = acervo.filter((l) => !l.disponivel);

  // reduce não serve só para somar: aqui ele agrupa por autor,
  // acumulando um objeto em vez de um número.
  const porAutor = acervo.reduce((mapa, livro) => {
    mapa[livro.autor] = (mapa[livro.autor] ?? 0) + 1;
    return mapa;
  }, {});

  // Math.max com spread em vez de um laço só para achar o maior.
  const maisLongo = acervo.reduce((maior, livro) =>
    livro.paginas > maior.paginas ? livro : maior
  );

  return {
    total: acervo.length,
    emprestados: emprestados.length,
    paginas: acervo.reduce((s, l) => s + l.paginas, 0),
    mediaPaginas: Math.round(acervo.reduce((s, l) => s + l.paginas, 0) / acervo.length),
    autores: Object.keys(porAutor).length,
    maisLongo: `${maisLongo.titulo} (${maisLongo.paginas}p)`,
  };
}

console.table(relatorio(livros));

// ---------------------------------------------------------------
// O detalhe que morde: ?? não é a mesma coisa que ||
// ---------------------------------------------------------------
// No contador acima, `|| 0` funcionaria por acaso. Em contador que
// pode legitimamente valer 0, não funciona:
const emprestimos = { "Vidas Secas": 0 };
console.log(emprestimos["Vidas Secas"] || 99); // 99 — errado, 0 é falsy
console.log(emprestimos["Vidas Secas"] ?? 99); // 0  — certo, existe

?? só cai no padrão quando o valor é null ou undefined; || cai também com 0, "" e false. Em contador, preço e campo de formulário — onde zero e string vazia são respostas válidas — usar || apaga o dado do usuário.

O que mais custa caro aqui não é a sintaxe, é a referência: atribuir um objeto a outra variável não copia nada, e mexer em um mexe no outro. Daí a importância do spread e do structuredClone quando a cópia precisa ser de verdade, e daí também o cuidado com cópia rasa, que resolve o primeiro nível e deixa os de dentro compartilhados. O ?. trata o problema vizinho — alcançar o que pode não existir sem derrubar a execução três níveis abaixo.

Fontes e leituras recomendadas

Exercícios

Exercício 1

O que a última linha imprime, e por quê? Escreva a correção de uma linha.

const original = { nome: "Ana", idade: 25 };

const copia = original;
copia.nome = "Maria";

console.log(original.nome);
Ver resposta

✓ Resposta: Imprime Maria. A linha const copia = original não copia o objeto — copia a referência, o endereço. As duas variáveis passam a apontar para o mesmo objeto na memória, então mexer em qualquer uma delas altera o mesmo dado. A correção é const copia = { ...original };, que cria um objeto novo com as mesmas propriedades. Note que o const não protege nada aqui: ele impede reatribuir a variável, não modificar o objeto para o qual ela aponta.

Exercício 2

Uma das duas linhas finais imprime Desenvolvedora e a outra imprime outra coisa. Qual é qual?

const pessoa = { nome: "Ana", profissao: "Desenvolvedora" };
const campo = "profissao";

console.log(pessoa[campo]);
console.log(pessoa.campo);
Ver resposta

✓ Resposta: A primeira imprime Desenvolvedora; a segunda, undefined. Nos colchetes, o que está dentro é avaliado: campo vira a string "profissao" e a propriedade é encontrada. Depois do ponto, o nome é literal: pessoa.campo procura uma propriedade chamada exatamente campo, que não existe. Daí a regra do artigo — ponto quando você sabe o nome ao escrever o código, colchetes quando o nome só se conhece em tempo de execução.

Exercício 3

A primeira linha funciona e a segunda quebra. Qual é a mensagem de erro exata, e qual operador a evita?

const config = { tema: { cor: "azul" } };

console.log(config.tema.cor);
console.log(config.idioma.codigo);
Ver resposta

✓ Resposta: A segunda lança TypeError: Cannot read properties of undefined (reading 'codigo'). O problema não é idioma não existir — ler uma propriedade inexistente devolve undefined sem reclamar. O erro é ler codigo de dentro desse undefined. Com o optional chaining, config.idioma?.codigo devolve undefined e a execução segue: o ?. interrompe a corrente assim que encontra undefined ou null, em vez de tentar o próximo acesso.

Exercício 4

O que Object.entries(precos) devolve, e o que significa o [, valor] no filter?

const precos = { notebook: 3500, mouse: 80, teclado: 200 };

console.log(Object.entries(precos).filter(([, valor]) => valor > 100));
Ver resposta

✓ Resposta: Object.entries devolve um array de pares: [["notebook", 3500], ["mouse", 80], ["teclado", 200]] — e é isso que permite usar filter, map e companhia sobre um objeto, que sozinho não tem esses métodos. O resultado do filtro é [["notebook", 3500], ["teclado", 200]]. O [, valor] é desestruturação de array com a primeira posição pulada: a vírgula sozinha descarta a chave e guarda só o segundo item do par.

Exercício 5

Qual é a saída das quatro chamadas, e quanto vale conta.saldo no fim?

const conta = {
  titular: "Carlos",
  saldo: 1000,
  depositar(valor) { this.saldo += valor; },
  sacar(valor) {
    if (valor > this.saldo) { console.log("Saldo insuficiente."); return; }
    this.saldo -= valor;
  },
};

conta.depositar(500);
conta.sacar(200);
conta.sacar(2000);
console.log(conta.saldo);
Ver resposta

✓ Resposta: Só a terceira chamada imprime algo — Saldo insuficiente. —, e no fim conta.saldo vale 1300. A conta: 1000 + 500 = 1500, menos 200 = 1300; o saque de 2000 é maior que 1300, então o return abandona o método antes de subtrair. Repare no papel do this: ele é o próprio objeto conta, e é o que permite ao método ler e alterar o saldo sem que o nome da variável externa apareça dentro dele.

Comentários

Mais em Javascript

Trabalhando com arquivos CSV
Trabalhando com arquivos CSV

CSV parece o formato mais simples que existe até o primeiro campo com vírgula…

Tratamento de erros em requisições HTTP
Tratamento de erros em requisições HTTP

Código que só funciona quando tudo dá certo não está pronto para produção…

Filas de Tarefas e Processamento Assíncrono
Filas de Tarefas e Processamento Assíncrono

A fila separa aceitar o pedido de executá-lo: a API confirma em milissegundos…