Objetos: estruturando dados do mundo real

[36] 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"

Falaremos mais sobre spread operator no próximo artigo.

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 Referências

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

Tratamento de Erros com try, catch e finally
Tratamento de Erros com try, catch e finally

Um catch vazio transforma falha em silêncio: a tela não reage, o log não…

Git avançado e fluxo de trabalho em equipe
Git avançado e fluxo de trabalho em equipe

Saber add, commit e push é o mínimo. O que aparece em projeto com mais gente é…

Express.js: o framework web do Node
Express.js: o framework web do Node

O mesmo endpoint que ocupava trinta linhas de if e regex cabe em quatro com…