La clase Object de JavaScript incluye un conjunto de métodos estáticos para trabajar con objetos: enumerar propiedades, copiar, congelar y crear objetos con prototipos controlados. Son las herramientas básicas para manipular objetos sin mutar los originales, siguiendo patrones de inmutabilidad.
Object.keys(), values() y entries()
Estos tres métodos devuelven arrays con las claves, los valores y los pares [clave, valor] de las propiedades propias enumerables de un objeto:
const producto = {
nombre: 'Teclado mecánico',
precio: 89,
stock: 15,
categoria: 'hardware'
};
console.log(Object.keys(producto));
// ['nombre', 'precio', 'stock', 'categoria']
console.log(Object.values(producto));
// ['Teclado mecánico', 89, 15, 'hardware']
console.log(Object.entries(producto));
// [['nombre', 'Teclado mecánico'], ['precio', 89], ...]
// Iterar con for...of y entries:
for (const [clave, valor] of Object.entries(producto)) {
console.log(`${clave}: ${valor}`);
}
// Casos de uso comunes:
// 1. Filtrar propiedades de un objeto
const soloStrings = Object.fromEntries(
Object.entries(producto).filter(([, v]) => typeof v === 'string')
);
console.log(soloStrings); // { nombre: 'Teclado mecánico', categoria: 'hardware' }
// 2. Transformar valores
const preciosConIva = Object.fromEntries(
Object.entries({ silla: 200, mesa: 400, lampara: 60 })
.map(([k, v]) => [k, v * 1.21])
);
console.log(preciosConIva); // { silla: 242, mesa: 484, lampara: 72.6 }
Object.assign(): copiar y fusionar objetos
Object.assign(destino, ...fuentes) copia las propiedades enumerables propias de las fuentes al destino, muando el objeto destino. Es útil pero hace una copia superficial:
// Fusionar configuraciones
const configBase = { host: 'localhost', puerto: 3000, debug: false };
const configDev = { debug: true, verbose: true };
const config = Object.assign({}, configBase, configDev);
console.log(config);
// { host: 'localhost', puerto: 3000, debug: true, verbose: true }
// Importante: assign MUTA el destino
const obj = { a: 1 };
Object.assign(obj, { b: 2 });
console.log(obj); // { a: 1, b: 2 } obj fue mutado
// Copia superficial (solo un nivel):
const original = { nombre: 'Ana', direccion: { ciudad: 'Madrid' } };
const copia = Object.assign({}, original);
copia.nombre = 'Luis'; // No afecta a original
copia.direccion.ciudad = 'Barcelona'; // SÍ afecta a original
console.log(original.nombre); // 'Ana' OK
console.log(original.direccion.ciudad); // 'Barcelona' PROBLEMA
// Hoy se prefiere el spread operator (más limpio):
const copiaMejor = { ...original };
// Para copia profunda, usar structuredClone:
const copiaTotal = structuredClone(original);
Object.freeze(): inmutabilidad superficial
Object.freeze(obj) impide agregar, eliminar o modificar propiedades del objeto. Como Object.assign(), la congelación es superficial:
const COLORES = Object.freeze({
PRIMARIO: '#007bff',
EXITO: '#28a745',
PELIGRO: '#dc3545',
CONFIG: { opacidad: 1 }
});
// Las modificaciones fallan silenciosamente (o lanzan TypeError en modo estricto)
COLORES.PRIMARIO = '#ff0000'; // Sin efecto
COLORES.NUEVO = '#ffffff'; // Sin efecto
delete COLORES.EXITO; // Sin efecto
console.log(COLORES.PRIMARIO); // '#007bff' sin cambios
// Freeze es superficial: los objetos anidados sí se pueden modificar
COLORES.CONFIG.opacidad = 0.5;
console.log(COLORES.CONFIG.opacidad); // 0.5 ¡modificado!
// Para congelar en profundidad:
function congelarProfundo(obj) {
Object.freeze(obj);
Object.values(obj).forEach(v => {
if (typeof v === 'object' && v !== null) congelarProfundo(v);
});
return obj;
}
Object.create(): prototipos explícitos
Object.create(proto, descriptores) crea un objeto cuyo prototipo es exactamente el que especificas, con control total sobre los descriptores de propiedad:
const proto = {
saludar() { return `Hola, soy ${this.nombre}`; },
toString() { return `[${this.tipo}: ${this.nombre}]`; }
};
const persona = Object.create(proto);
persona.nombre = 'Ana';
persona.tipo = 'Persona';
console.log(persona.saludar()); // "Hola, soy Ana"
console.log(String(persona)); // "[Persona: Ana]"
// Objeto sin prototipo (diccionario puro, sin toString, hasOwnProperty, etc.):
const mapa = Object.create(null);
mapa['constructor'] = 'valor'; // No colisiona con Object.prototype
console.log(mapa.toString); // undefined
Object.defineProperty(): control de descriptores
Para un control granular sobre cómo se comporta una propiedad (enumerable, configurable, writable):
const persona = { nombre: 'Ana' };
Object.defineProperty(persona, 'id', {
value: 42,
writable: false, // no se puede reasignar
enumerable: false, // no aparece en for...in ni Object.keys()
configurable: false // no se puede eliminar ni redefinir
});
console.log(persona.id); // 42
persona.id = 99; // Sin efecto en modo no estricto
console.log(Object.keys(persona)); // ['nombre'] id no aparece
// Getter/setter con defineProperty:
let _edad = 0;
Object.defineProperty(persona, 'edad', {
get() { return _edad; },
set(v) {
if (typeof v !== 'number' || v < 0) throw new Error('Edad inválida');
_edad = v;
},
enumerable: true,
configurable: true
});
persona.edad = 30;
console.log(persona.edad); // 30
En código moderno, Object.assign() se ha sustituido casi completamente por el spread operator en objetos ({ ...a, ...b }), que hace lo mismo con sintaxis más limpia. Object.freeze() y Object.defineProperty() siguen siendo necesarios cuando necesitas control granular sobre la mutabilidad.
