Dia 9 — Objetos
Criar e acessar propriedades
Objeto: a estrutura chave-valor
Objeto é o dicionário do JavaScript: cada propriedade é um par de chave e valor. Enquanto o array guarda itens em ordem, numerados a partir de 0, o objeto guarda o que se procura por nome. Um cadastro, uma linha de tabela, a resposta de uma API e a configuração de um programa são todos objetos.
A forma curta é {} com pares chave: valor separados por vírgula. A forma longa, new Object(), continua válida e quase nunca é usada.
const aluno = { id: 1, nome: 'Ana Souza', ativo: true, };
Chave pode ser qualquer string, com espaço e acento, desde que esteja entre aspas. Valor é qualquer coisa: número, texto, true, null, outro objeto, um array ou uma função.
A forma de criar objeto é a chave dentro das chaves: { nome: 'Ana' }. O [] cria lista; o {} cria o que o material chama de objeto, e cada par dentro dele é a propriedade — chave e valor.
Notação de ponto e notação de colchete
As duas notações fazem a mesma coisa. A de ponto é a mais curta e serve quando a chave é um identificador válido; a de colchete é a forma geral e aceita qualquer texto como chave.
const aluno = { nome: 'Ana', email: '[email protected]' }; aluno.nome; // 'Ana' aluno['email']; // '[email protected]' const campo = 'email'; aluno[campo]; // '[email protected]'
A notação de colchete é a única que funciona com chave que vem de uma variável. aluno[campo] funciona; aluno.campo não existe, porque a chave escrita com ponto vira o nome literal campo. É isso que faz a chave dinamica: o nome vem de fora, decidido em tempo de execução, e o objeto só é consultado depois.
| Notação | Assinatura | Quando usar |
|---|---|---|
| ponto | objeto.propriedade | chave fixa, sem espaço e que começa por letra |
| colchete | objeto['propriedade'] | chave com espaço, acento ou pontuação |
| colchete com variável | objeto[variavel] | chave escolhida em tempo de execução |
A segunda linha da tabela é a notação de colchete com a chave entre aspas: objeto["propriedade"], que é a forma geral e aceita qualquer texto — inclusive a chave que tem espaço dentro.
Uma chave que começa com número ('1') ou que é palavra reservada ('class') só funciona com colchete e com aspas. objeto.class é erro de sintaxe.
Criar, alterar e remover propriedades
Não existe declaração de propriedade: o objeto nasce com o que foi escrito e aceita propriedade nova a qualquer momento. As duas notações também escrevem.
const contato = {}; contato.telefone = '(81) 99999-0000'; // cria com ponto contato['cidade'] = 'Recife'; // cria com colchete delete contato.cidade; // remove e devolve true
delete devolve true quando conseguiu apagar e false quando a propriedade não existia. Não há como desfazer: apagar a chave não guarda o valor em lugar nenhum.
Propriedade que não existe não é erro. aluno.cpf devolve undefined, e o programa segue rodando como se a chave tivesse valor vazio.
const aluno = { nome: 'Ana' }; aluno.cpf; // undefined typeof aluno.cpf; // 'undefined' 'aluno.cpf' in aluno; // false — a chave não está lá
Esse silêncio é a origem de bug mais comum da linguagem: código que assume que a propriedade existe recebe undefined em vez de um aviso, e o TypeError só aparece mais abaixo, na linha que tentou usar o valor.
Copiar o objeto
Atribuição não copia. Copia a referência: as duas variáveis passam a apontar para o mesmo objeto, e mexer em uma muda a outra.
const original = { nome: 'Ana' }; const copia = original; copia.nome = 'Bruno'; original.nome; // 'Bruno' — mudou junto
Para copiar de verdade, use o spread dentro de {}, que monta um objeto novo com as mesmas propriedades.
const original = { nome: 'Ana', cidade: 'Recife' }; const copia = { ...original }; copia.nome = 'Bruno'; original.nome; // 'Ana' — o original ficou como estava
O spread é raso: as propriedades que são objetos ou arrays continuam apontando para o mesmo lugar. Copiar { ...produto } duplica a casca, e o produto.medidas continua sendo o mesmo objeto nos dois. Para clonar um nível a mais é preciso structuredClone, que funciona com objeto, array, Map, Set e Date.
constnão protege o conteúdo do objeto:const ficha = { ativo: true }permiteficha.ativo = false, porque oconsttrava a variável, não o que está dentro dela. Quando a intenção é travar o conteúdo, o caminho éObject.freeze, que impede a escrita no próprio nível — e só nele.
Ler propriedade de
undefinedou denulléTypeErrorna hora:usuario.nomecomusuarioinexistente quebra o programa. A leitura de propriedade inexistente em objeto que existe é silenciosa; a leitura em valor que não existe, não.
Exemplo
'use strict'; // ============================================================ // AULA 1 - Objeto: criar, acessar e remover propriedades // // O objeto é o dicionário do JavaScript: chave, valor. Este // exemplo mostra a forma curta, as duas notações de acesso, a // atribuição de propriedade nova e o `delete`. // ============================================================ // A forma curta: cada par é `chave: valor`, separado por vírgula. const aluno = { id: 1, nome: 'Ana Souza', email: '[email protected]', ativo: true, }; // Notação de ponto: a mais curta, para chave que é identificador válido. console.log('aluno.nome :', aluno.nome); console.log('aluno.ativo :', aluno.ativo); // Notação de colchete: a forma geral, aceita qualquer chave como texto. console.log("aluno['email']:", aluno['email']); // Com colchete a chave pode vir de uma variável. Com ponto, não. const campo = 'email'; console.log('aluno[campo] :', aluno[campo]); // Atribuir propriedade que ainda não existia: nada precisa ser declarado // antes. As duas notações também escrevem. aluno.cidade = 'Recife'; aluno['nota'] = 8.5; console.log('\nchaves depois das duas atribuições:', Object.keys(aluno).join(', ')); // Objeto vazio e preenchido depois, campo a campo. const contato = {}; contato.telefone = '(81) 99999-0000'; contato['cidade'] = 'Recife'; console.log('contato.telefone:', contato.telefone); // Propriedade inexistente NÃO dá erro: devolve `undefined`. console.log('\naluno.cpf (não existe) ->', aluno.cpf); console.log('typeof aluno.cpf ->', typeof aluno.cpf); // A mesma chave em dois objetos guarda valores independentes. const outro = { nome: 'Bruno Lima', email: '[email protected]' }; console.log('\nmesma chave, dois objetos:', aluno.nome, '|', outro.nome); // `delete` remove e devolve `true`. Não existe como desfazer. const apagado = delete aluno.ativo; console.log('\ndelete aluno.ativo devolveu:', apagado); console.log('chaves depois do delete:', Object.keys(aluno).join(', ')); // Cópia com o spread: objeto novo, o original intacto. const copia = { ...aluno }; copia.nome = 'Carla Dias'; console.log('\ndepois de editar a cópia -> cópia:', copia.nome, '| original:', aluno.nome); // Ler propriedade de algo que não é objeto é `TypeError`. É o erro que // trava mais formulário: o `value` do campo veio vazio e o código tentou // ler `.length` em `undefined`. O aviso vai para o stderr e o programa // continua rodando depois dele. console.log('\n--- o que acontece quando o objeto não existe ---'); try { const usuario = undefined; console.log(usuario.nome); } catch (erro) { console.error(erro.constructor.name + ': ' + erro.message); } console.log('--- o código depois do erro continua rodando ---');
Saída real
aluno.nome : Ana Souza aluno.ativo : true aluno['email']: [email protected] aluno[campo] : [email protected] chaves depois das duas atribuições: id, nome, email, ativo, cidade, nota contato.telefone: (81) 99999-0000 aluno.cpf (não existe) -> undefined typeof aluno.cpf -> undefined mesma chave, dois objetos: Ana Souza | Bruno Lima delete aluno.ativo devolveu: true chaves depois do delete: id, nome, email, cidade, nota depois de editar a cópia -> cópia: Carla Dias | original: Ana Souza --- o que acontece quando o objeto não existe --- --- o código depois do erro continua rodando ---
Desestruturacao e os metodos de objeto
Ler o objeto inteiro
Quando a chave é desconhecida — um relatório, uma validação, um formulário montado a partir de dados — o que se procura são as três listas: as chaves, os valores, ou os pares.
| Método | Assinatura | Devolve |
|---|---|---|
Object.keys | Object.keys(objeto) | array com as chaves, na ordem de inserção |
Object.values | Object.values(objeto) | array com os valores, na mesma ordem |
Object.entries | Object.entries(objeto) | array de pares [chave, valor] |
Object.fromEntries | Object.fromEntries([[chave, valor]]) | o caminho inverso: pares viram objeto |
const ficha = { id: 7, nome: 'Cadeira', preco: 749.9 }; Object.keys(ficha); // ['id', 'nome', 'preco'] Object.values(ficha); // [7, 'Cadeira', 749.9] Object.entries(ficha); // [['id', 7], ['nome', 'Cadeira'], ['preco', 749.9]]
Os três devolvem arrays novos. Mexer no array devolvido não altera o objeto, e recarregar Object.keys depois devolve a lista de novo, sem o que foi acrescentado.
O entries é o que percorre com for...of: cada volta entrega um par, que a desestruturação quebra em duas variáveis.
for (const [chave, valor] of Object.entries(ficha)) { console.log(chave, '=', valor); }
A ordem é sempre a de inserção para chave que não parece número. Chave que é a string '2' ou '1' vem antes das outras, porque o JavaScript trata chave numérica como índice e as coloca em ordem crescente. Para chaves em ordem previsível, use prefixo: 'campo_nome'.
Desestruturação
A desestruturação entrega as propriedades do objeto em variáveis, sem escrever o nome do objeto em cada linha. É a forma curta de "pegue estes campos". O termo em inglês é destructure, e o que ele faz é o oposto do spread: o spread junta, a desestruturação separa.
const { nome, preco } = ficha; console.log(nome, preco);
Três variações resolvem os três casos que aparecem:
| Forma | O que faz |
|---|---|
{ nome } | a variável nome recebe ficha.nome |
{ nome: titulo } | renomeia: a variável titulo recebe ficha.nome |
{ desconto = 0 } | valor padrão quando a chave não existe |
const { nome: titulo, desconto = 0 } = ficha;
O padrão só age quando a chave está ausente ou vale undefined. Se a chave existir com valor 0, o 0 é o que fica.
Para objeto aninhado, a desestruturação desce em dois níveis, e o nome do nível intermediário não precisa de variável:
const { medidas: { largura } } = produto;
Para o resto, use o resto: const { nome, ...resto } = ficha tira nome e entrega todo o resto em resto, que é um objeto novo.
Mesclar objetos
Object.assign e o spread juntam dois objetos num terceiro. A chave repetida do segundo ganha, e o resultado é sempre objeto novo. O spread em objeto é o mesmo ... do dia 7: aqui ele vai dentro das chaves e copia as propriedades.
| Forma | Assinatura | Cuidado |
|---|---|---|
| spread | { ...padrao, ...doUsuario } | devolve objeto novo |
assign | Object.assign(alvo, ...fonte) | mexe no alvo, que é o primeiro argumento |
const padrao = { tema: 'claro', fonte: 16, idioma: 'pt-BR' }; const doUsuario = { tema: 'escuro' }; const tema = { ...padrao, ...doUsuario }; // tema: 'escuro'
Object.assign(padrao, doUsuario) tem o mesmo efeito no conteúdo, mas altera padrao para sempre — e padrao foi declarado com const, o que não impede nada aqui. Para mesclar sem efeito colateral, o primeiro argumento precisa ser {}.
Object.assign só copia propriedades próprias e enumeráveis. Método definido no objeto também é copiado como valor, mas JSON.stringify não o serializa.
O método abreviado é o que dispensa o par nome: nome no objeto. Com a forma longa, nome: nome e somar: function(a, b) { }; com a abreviada, somar(a, b) { } — o mesmo resultado, e o objeto fica legível.
const calculadora = { somar(a, b) { return a + b; }, // método abreviado somarAntigo: function (a, b) { return a + b; }, // a forma longa };
Verificar e travar
Antes de perguntar se a chave existe, vale saber o que a chave vale. O typeof objeto devolve 'object' para objeto, array, função, null e Date — todos eles. A única diferença entre eles é o que Array.isArray e instanceof respondem; o typeof sozinho não separa nada.
| Método | Assinatura | Devolve |
|---|---|---|
in | 'chave' in objeto | true se a chave existe, inclusive as herdadas |
Object.hasOwn | Object.hasOwn(objeto, 'chave') | true só se a chave é do próprio objeto |
hasOwnProperty | objeto.hasOwnProperty('chave') | o mesmo; é o método antigo |
Object.freeze | Object.freeze(objeto) | trava a escrita no próprio nível |
'preco' in ficha; // true 'toString' in {}; // true — veio do protótipo Object.hasOwn({}, 'toString'); // false
in e hasOwn discordam justamente nos métodos que todo objeto tem herdados: toString, constructor, hasOwnProperty. Para perguntar se o dado está no objeto, Object.hasOwn é a resposta certa.
Object.freeze impede escrita nas propriedades dele. Com 'use strict' no topo do arquivo, a tentativa de escrever lança TypeError; sem o modo estrito, a escrita é ignorada em silêncio.
| Chamada | Efeito |
|---|---|
Object.freeze(objeto) | trava o nível de fora |
Object.isFrozen(objeto) | true se o próprio nível está travado |
Object.seal(objeto) | não aceita chave nova, mas aceita alterar valor |
Object.freeze com objeto dentro | o interno continua mutável, é raso |
JSON: o objeto indo e voltando pela rede
JSON.stringify transforma objeto em texto e JSON.parse traz o texto de volta. É o que toda comunicação entre o navegador e o servidor faz, e o que o localStorage usa para guardar objeto.
| Função | Assinatura | Devolve |
|---|---|---|
stringify | JSON.stringify(objeto, filtro, espaco) | texto |
parse | JSON.parse(texto) | objeto |
parse com padrão | JSON.parse(texto, reviver) | objeto, com função de transformação |
const linha = JSON.stringify(ficha, null, 2); const deVolta = JSON.parse(linha);
O segundo argumento é o filtro de propriedades e o terceiro é a indentação, em espaços. null no lugar do filtro significa "todos"; um array de nomes de chave inclui só essas.
O que o JSON não leva: função, undefined, símbolo e Date vira texto. A função é simplesmente ignorada, sem erro e sem aviso — o objeto serializado sai menor do que o original, e o código que dependia do método só quebra depois, do outro lado da rede.
Texto que não é JSON válido é SyntaxError no parse, e o objeto que se obtinha antes da chamada se perde.
localStoragesó aceita texto.localStorage.setItem('user', objeto)grava a palavra[object Object], que volta inútil nogetItem. O caminho ésetItem('user', JSON.stringify(objeto))eJSON.parse(getItem('user'))na leitura — e otry/catchem volta, porque um dado gravado por uma versão antiga do código pode não ser mais parseável.
JSON.stringifyeJSON.parsenão são o mesmo questructuredClone: o segundo copiaDate,MapeSete mantém o tipo, o que o JSON não consegue. Para mandar dado a um servidor, o formato é JSON; para clonar objeto dentro do próprio programa, éstructuredClone.
Exemplo
'use strict'; // ============================================================ // AULA 2 - Desestruturação e os métodos que leem o objeto // // `Object.keys`, `Object.values` e `Object.entries` leem o objeto // inteiro; a desestruturação entrega as propriedades em variáveis; // `assign` e spread mesclam; `freeze` trava; `JSON` vai e volta // pela rede. // ============================================================ // Registro simples, o mais comum do dia a dia. const ficha = { id: 7, nome: 'Cadeira de escritorio', preco: 749.9, ativo: true, }; // --- Ler o objeto inteiro --- console.log('keys :', Object.keys(ficha)); console.log('values :', Object.values(ficha)); console.log('entries:', Object.entries(ficha)); // `for...of` sobre `entries`: chave e valor na mesma volta do laço. console.log('\npares chave/valor:'); for (const [chave, valor] of Object.entries(ficha)) { console.log(` ${chave} = ${valor}`); } // Os três devolvem array NOVO: mexer no array não mexe no objeto. const chaves = Object.keys(ficha); chaves.push('sorteado'); console.log('\narray devolvido é novo:', chaves.length, 'itens, o objeto continua com', Object.keys(ficha).length, 'chaves'); // --- Desestruturação --- const { nome, preco } = ficha; console.log('\nnome:', nome, '| preco:', preco); // Com ':' a variável ganha outro nome. const { nome: nomeDoProduto } = ficha; console.log('variável renomeada, nomeDoProduto:', nomeDoProduto); // Com '=' vem o valor padrão, usado quando a chave não existe. const { desconto = 0 } = ficha; console.log('desconto padrão quando a chave não existe:', desconto); // Desestruturar junto com o objeto aninhado. const produto = { codigo: 'C-100', medidas: { largura: 60, altura: 110 }, descrever() { return `${this.codigo}: ${this.medidas.largura}x${this.medidas.altura} cm`; }, }; const { medidas: { altura } } = produto; console.log('altura vinda do objeto aninhado:', altura); console.log('método do objeto:', produto.descrever()); // --- Mesclar e travar --- const padrao = { tema: 'claro', fonte: 16, idioma: 'pt-BR' }; const doUsuario = { tema: 'escuro' }; // `assign` MODIFICA o primeiro argumento e devolve. // Com `{}` no lugar do `padrao`, o original fica intacto. const porAssign = Object.assign({}, padrao, doUsuario); console.log('\nmesclado:', porAssign); // O spread faz a mesma coisa sem depender de efeito colateral. const porSpread = { ...padrao, ...doUsuario }; console.log('mesclado com spread:', porSpread); console.log('o padrão continua intacto:', JSON.stringify(padrao)); // `freeze` trava o nível de fora. Com `'use strict'` a escrita em objeto // congelado lança `TypeError` em vez de ser ignorada em silêncio. const congelado = Object.freeze({ versao: 1, tags: ['js', 'web'] }); try { congelado.versao = 2; } catch (erro) { console.error('tentou escrever no congelado -> ' + erro.constructor.name); } // O `freeze` é RASO: `tags` é um array, ou seja, outro nível, e passa. congelado.tags.push('dom'); console.log('\nfreeze raso: versão continua', congelado.versao, '| tags, que são outro nível:', congelado.tags.join(', ')); // --- Verificar e serializar --- console.log('\n"preco" in ficha :', 'preco' in ficha); console.log('"toString" in {} :', 'toString' in {}, '(vem do protótipo)'); console.log('Object.hasOwn({}, "toString"):', Object.hasOwn({}, 'toString')); // `JSON.stringify` vira texto; o terceiro argumento indenta. const linha = JSON.stringify(ficha, null, 2); console.log('\ntexto gerado:'); console.log(linha); // `JSON.parse` volta para objeto. É o caminho de todo `fetch` e // do `localStorage`. const deVolta = JSON.parse(linha); console.log('type:', typeof deVolta, '| deVolta.preco:', deVolta.preco); // Função não vai para o JSON: é preciso montar o que será enviado. const comMetodo = JSON.stringify(produto, null, 2); console.log('\nJSON do objeto com método:\n' + comMetodo);
Saída real
keys : [ 'id', 'nome', 'preco', 'ativo' ]
values : [ 7, 'Cadeira de escritorio', 749.9, true ]
entries: [
[ 'id', 7 ],
[ 'nome', 'Cadeira de escritorio' ],
[ 'preco', 749.9 ],
[ 'ativo', true ]
]
pares chave/valor:
id = 7
nome = Cadeira de escritorio
preco = 749.9
ativo = true
array devolvido é novo: 5 itens, o objeto continua com 4 chaves
nome: Cadeira de escritorio | preco: 749.9
variável renomeada, nomeDoProduto: Cadeira de escritorio
desconto padrão quando a chave não existe: 0
altura vinda do objeto aninhado: 110
método do objeto: C-100: 60x110 cm
mesclado: { tema: 'escuro', fonte: 16, idioma: 'pt-BR' }
mesclado com spread: { tema: 'escuro', fonte: 16, idioma: 'pt-BR' }
o padrão continua intacto: {"tema":"claro","fonte":16,"idioma":"pt-BR"}
freeze raso: versão continua 1 | tags, que são outro nível: js, web, dom
"preco" in ficha : true
"toString" in {} : true (vem do protótipo)
Object.hasOwn({}, "toString"): false
texto gerado:
{
"id": 7,
"nome": "Cadeira de escritorio",
"preco": 749.9,
"ativo": true
}
type: object | deVolta.preco: 749.9
JSON do objeto com método:
{
"codigo": "C-100",
"medidas": {
"largura": 60,
"altura": 110
}
}