El objeto Object en JavaScript ha recibido métodos nuevos en cada versión reciente del estándar. Algunos resuelven antipatrones clásicos, otros añaden capacidades que antes requerían bibliotecas externas. Esta guía repasa los más útiles con ejemplos prácticos.
Object.hasOwn (ES2022)
El método Object.hasOwn(obj, prop) es la forma moderna y segura de comprobar si una propiedad pertenece al propio objeto (no heredada). Reemplaza al antipatrón de obj.hasOwnProperty(prop), que falla si el objeto no tiene ese método en su cadena de prototipos:
// Antipatrón clásico falla con Object.create(null)
const obj = Object.create(null);
obj.nombre = 'Ana';
// obj.hasOwnProperty('nombre') // TypeError: obj.hasOwnProperty is not a function
// Forma segura antigua
Object.prototype.hasOwnProperty.call(obj, 'nombre'); // true
// ES2022: simple y seguro
console.log(Object.hasOwn(obj, 'nombre')); // true
console.log(Object.hasOwn(obj, 'toString')); // false
// Útil al iterar con for...in
const config = { debug: true, timeout: 5000 };
for (const clave in config) {
if (Object.hasOwn(config, clave)) {
console.log(clave, config[clave]);
}
}
Object.fromEntries
Object.fromEntries (ES2019) convierte un array de pares [clave, valor] en un objeto. Es el inverso de Object.entries y permite transformar objetos con el pipeline entries ? map/filter ? fromEntries:
// Filtrar propiedades con valor truthy
const config = { debug: false, timeout: 5000, apiKey: '', host: 'localhost' };
const configLimpia = Object.fromEntries(
Object.entries(config).filter(([_, v]) => Boolean(v))
);
console.log(configLimpia); // { timeout: 5000, host: 'localhost' }
// Transformar valores (normalizar a minúsculas)
const headers = { 'Content-Type': 'application/json', 'X-Auth': 'Bearer token' };
const headersNorm = Object.fromEntries(
Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v])
);
console.log(headersNorm);
// { 'content-type': 'application/json', 'x-auth': 'Bearer token' }
// También funciona con Map
const mapa = new Map([['a', 1], ['b', 2], ['c', 3]]);
console.log(Object.fromEntries(mapa)); // { a: 1, b: 2, c: 3 }
structuredClone: clonar objetos profundamente
structuredClone (disponible en Node 17+ y todos los navegadores modernos) realiza una copia profunda real de un objeto, soportando fechas, conjuntos, mapas, arrays tipados y referencias circulares:
// El spread solo hace copia superficial
const original = {
nombre: 'Ana',
nacimiento: new Date('1990-05-15'),
permisos: new Set(['leer', 'escribir']),
config: { tema: 'oscuro', idioma: 'es' }
};
// Spread solo primer nivel
const spread = { ...original };
spread.config.tema = 'claro';
console.log(original.config.tema); // 'claro' ¡mutado!
// structuredClone copia profunda real
const clon = structuredClone(original);
clon.config.tema = 'claro';
console.log(original.config.tema); // 'oscuro' sin cambios
// Soporta tipos especiales
console.log(clon.nacimiento instanceof Date); // true
console.log(clon.permisos instanceof Set); // true
// Soporte de referencias circulares
const circular = { a: 1 };
circular.self = circular;
const clonCircular = structuredClone(circular);
console.log(clonCircular.self === clonCircular); // true
Object.freeze vs Object.seal
Ambos métodos restringen las modificaciones a un objeto, pero con distintos niveles de restricción:
// Object.freeze: sin añadir, modificar ni eliminar propiedades
const COLORES = Object.freeze({
PRIMARIO: '#0066cc',
SECUNDARIO: '#ff6600',
ERROR: '#cc0000',
});
COLORES.PRIMARIO = '#000000'; // Silencioso en modo no-strict, TypeError en strict
console.log(COLORES.PRIMARIO); // '#0066cc' sin cambios
// Object.seal: no añadir ni eliminar, pero SÍ modificar existentes
const estado = Object.seal({
usuario: null,
autenticado: false,
rol: 'invitado',
});
estado.autenticado = true; // OK se puede modificar
estado.nuevoCampo = 'valor'; // Ignorado (TypeError en strict)
delete estado.rol; // Ignorado (TypeError en strict)
console.log(estado); // { usuario: null, autenticado: true, rol: 'invitado' }
// Comprobar el estado
console.log(Object.isFrozen(COLORES)); // true
console.log(Object.isSealed(estado)); // true
Object.getOwnPropertyDescriptors
Devuelve todos los descriptores de propiedades propias de un objeto, incluyendo getters, setters y atributos como enumerable o writable. Es esencial para clonar objetos con accessors o mezclar objetos preservando descriptores:
const fuente = {
_nombre: 'Ana',
get nombre() { return this._nombre.toUpperCase(); },
set nombre(v) { this._nombre = v.trim(); }
};
// Object.assign pierde los getters/setters (los evalúa)
const copiaMal = Object.assign({}, fuente);
console.log(Object.getOwnPropertyDescriptor(copiaMal, 'nombre'));
// { value: 'ANA', ... } getter perdido
// Con getOwnPropertyDescriptors se preservan
const copiaBien = Object.defineProperties(
{},
Object.getOwnPropertyDescriptors(fuente)
);
copiaBien.nombre = ' Pedro ';
console.log(copiaBien.nombre); // 'PEDRO' getter/setter funcionan
Object.assign con múltiples fuentes
Un uso avanzado menos conocido: combinar un objeto de defaults con opciones del usuario de forma segura, sin mutar el objeto de defaults:
const DEFAULTS = Object.freeze({
timeout: 5000,
reintentos: 3,
debug: false,
headers: { 'Content-Type': 'application/json' },
});
function crearCliente(opciones = {}) {
// Nuevo objeto, defaults no mutados
const config = Object.assign({}, DEFAULTS, opciones);
// Los objetos anidados siguen siendo shallow merge:
config.headers = Object.assign({}, DEFAULTS.headers, opciones.headers);
return config;
}
const cliente = crearCliente({ timeout: 10000, headers: { 'X-Token': 'abc' } });
console.log(cliente.timeout); // 10000
console.log(cliente.headers); // { 'Content-Type': 'application/json', 'X-Token': 'abc' }
console.log(DEFAULTS.timeout); // 5000 sin cambios
Estos métodos cubren la mayoría de operaciones habituales con objetos en JavaScript moderno. Object.hasOwn simplifica comprobaciones seguras, Object.fromEntries permite transformaciones pipelines, structuredClone elimina la necesidad de JSON.parse/stringify para clonado profundo, y los descriptores dan control total sobre las propiedades.
