Manipulando objetos
Esta aula aborda técnicas fundamentais para manipular objetos em JavaScript: Object.keys/values/entries para iteração, spread operator para cópia e mesclagem, Object.freeze para imutabilidade, e a diferença entre cópia rasa e profunda. Com exemplos práticos e exercícios, você aprenderá a trabalhar com objetos de forma eficiente e segura.
Objetos são a espinha dorsal do JavaScript moderno. Saber manipulá-los corretamente é essencial para escrever código limpo e previsível. Nesta aula, exploraremos quatro tópicos que vão desde iteração e cópia até controle de mutabilidade. Prepare-se para dominar técnicas que você usará diariamente.
Vamos começar com métodos estáticos que transformam objetos em arrays, passando pelo operador spread que simplifica cópias e mesclagens, depois veremos como congelar objetos para evitar alterações acidentais e, por fim, entenderemos a diferença crucial entre cópia rasa e profunda. Cada seção inclui exemplos práticos para fixar o conteúdo.
Object.keys/values/entries
Esses três métodos estáticos do objeto Object permitem extrair as chaves, valores ou pares chave-valor de um objeto como arrays. Eles são especialmente úteis para iterar sobre objetos, já que objetos não são diretamente iteráveis com for...of (a menos que implementem o protocolo de iteração).
Object.keys(obj) retorna um array com as chaves enumeráveis próprias do objeto. Object.values(obj) retorna os valores correspondentes. Object.entries(obj) retorna um array de arrays, cada subarray com [chave, valor]. Esses métodos ignoram propriedades da cadeia de protótipos e são amplamente usados em combinação com forEach, map, filter, etc.
const pessoa = { nome: 'Ana', idade: 30, cidade: 'São Paulo' };
console.log(Object.keys(pessoa)); // ['nome', 'idade', 'cidade']
console.log(Object.values(pessoa)); // ['Ana', 30, 'São Paulo']
console.log(Object.entries(pessoa));
// [ ['nome', 'Ana'], ['idade', 30], ['cidade', 'São Paulo'] ]
// Iterando com forEach:
Object.entries(pessoa).forEach(([chave, valor]) => {
console.log(`${chave}: ${valor}`);
});Spread em objetos
O operador spread (...) foi introduzido no ES2018 para objetos, permitindo copiar propriedades de um objeto para outro de forma concisa. É uma alternativa moderna ao Object.assign. A sintaxe é: { ...objetoFonte }.
O spread cria uma cópia rasa (shallow copy) das propriedades enumeráveis próprias. Também é usado para mesclar objetos: { ...obj1, ...obj2 }. Se houver conflito de chaves, o último objeto sobrescreve o anterior. O spread é muito útil para criar novos objetos sem modificar os originais, seguindo o princípio da imutabilidade.
const dados = { a: 1, b: 2 };
const copia = { ...dados };
console.log(copia); // { a: 1, b: 2 }
console.log(copia === dados); // false (objetos diferentes)
const extras = { b: 3, c: 4 };
const mesclado = { ...dados, ...extras };
console.log(mesclado); // { a: 1, b: 3, c: 4 }
// Cuidado: spread é raso!
const aninhado = { x: { y: 10 } };
const copiaAninhada = { ...aninhado };
copiaAninhada.x.y = 20;
console.log(aninhado.x.y); // 20 (referência compartilhada)Freeze
Object.freeze(obj) torna um objeto imutável: não é possível adicionar, remover ou modificar propriedades existentes. Além disso, as propriedades tornam-se não configuráveis e não graváveis (writable: false). É uma forma de garantir que um objeto não seja alterado acidentalmente, útil para constantes ou configurações.
Importante: Object.freeze é raso. Se o objeto contiver outros objetos (aninhados), estes ainda podem ser modificados, a menos que também sejam congelados. Para imutabilidade profunda, é necessário congelar recursivamente ou usar bibliotecas como Immutable.js. O método retorna o próprio objeto congelado.
const config = { api: 'https://api.exemplo.com', timeout: 5000 };
Object.freeze(config);
config.timeout = 3000; // Ignorado (modo estrito: TypeError)
console.log(config.timeout); // 5000
// Raso: objetos aninhados não são congelados
const obj = { interno: { valor: 1 } };
Object.freeze(obj);
obj.interno.valor = 2; // Funciona!
console.log(obj.interno.valor); // 2Cópia rasa vs profunda
Entender a diferença entre cópia rasa (shallow copy) e cópia profunda (deep copy) é crucial para evitar bugs sutis. Uma cópia rasa duplica apenas o primeiro nível do objeto; propriedades que são objetos (ou arrays) permanecem como referências compartilhadas entre o original e a cópia. Já a cópia profunda cria uma nova instância independente para cada nível, incluindo objetos aninhados.
Para cópias rasas, podemos usar spread ({...obj}), Object.assign({}, obj) ou Array.from() para arrays. Para cópias profundas, existem várias abordagens: JSON.parse(JSON.stringify(obj)) (limitado: não suporta funções, undefined, Symbol, datas, etc.), ou funções recursivas manuais, ou bibliotecas como Lodash (_.cloneDeep). Em ambientes modernos, também temos structuredClone() (navegadores e Node.js 17+).
const original = { a: 1, b: { c: 2 } };
// Cópia rasa
const rasa = { ...original };
rasa.b.c = 999;
console.log(original.b.c); // 999 (afetado)
// Cópia profunda com JSON
const profunda = JSON.parse(JSON.stringify(original));
profunda.b.c = 777;
console.log(original.b.c); // 999 (não afetado)
// structuredClone (moderno)
const profunda2 = structuredClone(original);
profunda2.b.c = 555;
console.log(original.b.c); // 999Boas práticas e observações finais
Sempre prefira imutabilidade quando possível: ao invés de modificar um objeto existente, crie um novo com as alterações (spread, Object.assign). Use Object.freeze para constantes e configurações. Lembre-se de que a cópia rasa é suficiente para objetos planos; para objetos aninhados, avalie se a cópia profunda é necessária. Evite JSON.parse(JSON.stringify) para dados que contenham funções, undefined ou datas; prefira structuredClone ou bibliotecas especializadas.
Referências
- MDN: Object.keys()
- MDN: Object.values()
- MDN: Object.entries()
- MDN: Spread syntax
- MDN: Object.freeze()
- MDN: Shallow copy
- MDN: Deep copy
- MDN: structuredClone()
Exercícios
Dado o objeto
const carro = { marca: 'Toyota', modelo: 'Corolla', ano: 2020 }, useObject.entriespara exibir no console cada chave e valor no formato "chave: valor".✓ Resposta:const carro = { marca: 'Toyota', modelo: 'Corolla', ano: 2020 }; Object.entries(carro).forEach(([chave, valor]) => { console.log(`${chave}: ${valor}`); });Crie uma cópia rasa do objeto
const original = { a: 1, b: { c: 2 } }usando spread. Modifique a propriedadecdo objeto aninhado na cópia para 99 e verifique se o original foi alterado. Explique por quê.✓ Resposta:const original = { a: 1, b: { c: 2 } }; const copia = { ...original }; copia.b.c = 99; console.log(original.b.c); // 99 // O original foi alterado porque o spread é raso: a propriedade b é uma referência para o mesmo objeto.Use
Object.freezepara congelar o objetoconst config = { host: 'localhost', port: 8080 }. Tente alterar a propriedadeportpara 9090. O que acontece? Verifique se o objeto está congelado comObject.isFrozen.✓ Resposta:const config = { host: 'localhost', port: 8080 }; Object.freeze(config); config.port = 9090; // Em modo estrito: TypeError; em modo normal: ignorado console.log(config.port); // 8080 console.log(Object.isFrozen(config)); // trueImplemente uma função
deepFreeze(obj)que congela recursivamente todas as propriedades de um objeto (incluindo objetos aninhados). Teste com um objeto que contenha outro objeto.✓ Resposta:function deepFreeze(obj) { Object.freeze(obj); for (let chave of Object.keys(obj)) { const valor = obj[chave]; if (valor !== null && (typeof valor === 'object' || typeof valor === 'function')) { deepFreeze(valor); } } return obj; } const obj = { a: 1, b: { c: 2 } }; deepFreeze(obj); obj.b.c = 99; // Ignorado console.log(obj.b.c); // 2Dado o objeto
const usuario = { nome: 'João', endereco: { cidade: 'Rio', uf: 'RJ' } }, crie uma cópia profunda usandostructuredClonee altere a cidade na cópia para 'Niterói'. Verifique se o original não foi alterado.✓ Resposta:const usuario = { nome: 'João', endereco: { cidade: 'Rio', uf: 'RJ' } }; const copia = structuredClone(usuario); copia.endereco.cidade = 'Niterói'; console.log(usuario.endereco.cidade); // 'Rio' (não alterado) console.log(copia.endereco.cidade); // 'Niterói'