Métodos y propiedades de los objetos en JavaScript

En esta página
Enterate del próximo post
Un mail cuando publico algo nuevo. Sin spam y te podés dar de baja cuando quieras.

Un mail cuando publico algo nuevo. Sin spam y te podés dar de baja cuando quieras.
En el post anterior conté cómo el objeto literal fue creciendo con el lenguaje. Lo que quedó pendiente es la otra mitad: qué se puede hacer con un objeto una vez que existe.
Cada vez que necesitaba recordar si Object.entries devolvía un array de arrays o un objeto, terminaba abriendo cuatro pestañas de MDN. Así que armé la referencia que me hubiera servido, agrupada por "para qué sirve" cada uno y no por orden alfabético.
Una aclaración sobre las versiones: casi todo lo que sigue es de ES5 (2009) o de ES2015. Las incorporaciones posteriores son pocas y muy puntuales, así que si algo te suena raro, probablemente sea de los últimos años.
Los cuatro métodos que más uso. Todos ignoran las propiedades heredadas y las no enumerables: solo miran lo que el objeto tiene puesto.
| Método | Versión | Parámetros | Devuelve |
|---|---|---|---|
Object.keys(obj) | ES5 | obj: object | string[] con las claves propias enumerables |
Object.values(obj) | ES2017 | obj: object | any[] con los valores, en el mismo orden |
Object.entries(obj) | ES2017 | obj: object | [string, any][], un array de pares |
Object.fromEntries(iterable) | ES2019 | iterable: iterable de pares clave-valor | un object nuevo |
El combo entries + map + fromEntries es mi forma estándar de transformar un objeto sin escribir un for:
const filtros = { tag: "javascript", pagina: 2, orden: "" };
// Saco los valores vacíos antes de armar el querystring
const limpios = Object.fromEntries(
Object.entries(filtros).filter(([, valor]) => valor !== ""),
);
// { tag: "javascript", pagina: 2 }Un detalle del orden: las claves salen primero las que parecen enteros, en orden numérico, y después el resto en el orden en que se insertaron. No es alfabético y no conviene depender de eso.
| Método | Versión | Parámetros | Devuelve |
|---|---|---|---|
Object.assign(destino, ...fuentes) | ES2015 | destino: object, fuentes: uno o más object | el mismo destino, ya mutado |
Object.create(proto, descriptores?) | ES5 | proto: object o null, descriptores: object opcional | un object nuevo |
structuredClone(valor) | No es de ECMAScript | valor: cualquier valor serializable | una copia profunda |
Object.assign muta el primer argumento, y ahí se esconde el bug clásico: si le pasás tu objeto de configuración por defecto como destino, lo estás pisando para toda la aplicación. Por eso hoy uso spread, que siempre crea uno nuevo:
const PORDEFECTO = { tema: "oscuro", idioma: "es" };
// Mal: PORDEFECTO queda modificado para siempre
const configMala = Object.assign(PORDEFECTO, { idioma: "en" });
// Bien: objeto nuevo, PORDEFECTO intacto
const config = { ...PORDEFECTO, idioma: "en" };Los dos hacen copia superficial: si adentro hay otro objeto, se comparte la referencia. structuredClone es la salida cuando necesito una copia real, y vale aclarar que no es parte del lenguaje sino del entorno (navegadores y Node 17 en adelante).
| Método | Versión | Parámetros | Devuelve |
|---|---|---|---|
Object.hasOwn(obj, clave) | ES2022 | obj: object, clave: string o symbol | boolean, solo propiedades propias |
obj.hasOwnProperty(clave) | ES3 | clave: string o symbol | boolean, solo propiedades propias |
clave in obj | ES3 (operador) | — | boolean, también las heredadas |
Object.getOwnPropertyNames(obj) | ES5 | obj: object | string[], incluidas las no enumerables |
Object.getOwnPropertySymbols(obj) | ES2015 | obj: object | symbol[] |
obj.propertyIsEnumerable(clave) | ES3 | clave: string o symbol | boolean |
Object.hasOwn llegó tarde pero llegó por una razón concreta. hasOwnProperty se hereda del prototipo, así que se rompe en los dos casos en que más lo necesitás: un objeto creado sin prototipo, y un objeto que viene de afuera y tiene una clave con ese mismo nombre.
const datos = JSON.parse('{"hasOwnProperty": "sorpresa"}');
datos.hasOwnProperty("id"); // TypeError: no es una función
Object.hasOwn(datos, "id"); // false, y sigue de largoLa regla que uso: Object.hasOwn para saber si el objeto trae el dato, e in solo cuando de verdad me importa la cadena de prototipos.
| Método | Versión | Parámetros | Devuelve |
|---|---|---|---|
Object.getPrototypeOf(obj) | ES5 | obj: object | el prototipo: un object o null |
Object.setPrototypeOf(obj, proto) | ES2015 | obj: object, proto: object o null | el mismo obj |
Object.create(proto) | ES5 | proto: object o null | un object nuevo que delega en proto |
proto.isPrototypeOf(obj) | ES3 | obj: object | boolean |
Object.create(null) es el caso que más me sirvió en la práctica: devuelve un objeto sin ninguna herencia, ideal para usar como diccionario. Nada de toString ni constructor apareciendo donde no los llamaste.
const contador = Object.create(null);
contador["toString"] = 1; // acá es una clave más, no pisa nada
Object.keys(contador); // ["toString"]Sobre setPrototypeOf: funciona, pero cambiarle el prototipo a un objeto que ya está en uso obliga a los motores a tirar abajo las optimizaciones que habían hecho sobre él. Si podés definir el prototipo al crearlo, mejor.
Acá se sale del uso cotidiano y se entra en la maquinaria. Toda propiedad, aunque la escribas con dos puntos en un literal, tiene detrás un descriptor.
| Método | Versión | Parámetros | Devuelve |
|---|---|---|---|
Object.defineProperty(obj, clave, desc) | ES5 | obj: object, clave: string o symbol, desc: descriptor | el mismo obj |
Object.defineProperties(obj, descs) | ES5 | obj: object, descs: object de descriptores | el mismo obj |
Object.getOwnPropertyDescriptor(obj, clave) | ES5 | obj: object, clave: string o symbol | el descriptor, o undefined |
Object.getOwnPropertyDescriptors(obj) | ES2017 | obj: object | un object con todos los descriptores |
El descriptor es un objeto común con estos atributos:
| Atributo | Tipo | Por defecto | Para qué |
|---|---|---|---|
value | cualquiera | undefined | el valor de la propiedad |
writable | boolean | false | si se puede reasignar |
get | function o undefined | undefined | getter |
set | function o undefined | undefined | setter |
enumerable | boolean | false | si aparece en Object.keys y en el spread |
configurable | boolean | false | si se puede redefinir o borrar |
Ojo con los valores por defecto: una propiedad creada con defineProperty nace en false en todo, mientras que una escrita normalmente nace en true. Es la diferencia entre una propiedad visible y una oculta:
const usuario = { nombre: "Fernando" };
Object.defineProperty(usuario, "tokenInterno", {
value: "abc123",
enumerable: false, // no sale en keys, ni en spread, ni en JSON
});
Object.keys(usuario); // ["nombre"]
JSON.stringify(usuario); // {"nombre":"Fernando"}
usuario.tokenInterno; // "abc123"Tres niveles de protección, del más fuerte al más suave. Todos son superficiales: alcanzan al objeto, no a lo que tenga adentro.
| Método | Versión | Parámetros | Devuelve |
|---|---|---|---|
Object.freeze(obj) | ES5 | obj: object | el mismo obj, congelado |
Object.seal(obj) | ES5 | obj: object | el mismo obj, sellado |
Object.preventExtensions(obj) | ES5 | obj: object | el mismo obj |
Object.isFrozen(obj) | ES5 | obj: object | boolean |
Object.isSealed(obj) | ES5 | obj: object | boolean |
Object.isExtensible(obj) | ES5 | obj: object | boolean |
const RUTAS = Object.freeze({
blog: "/blog",
contacto: "/contacto",
});
RUTAS.blog = "/otra-cosa"; // en silencio no hace nada; con "use strict" tira TypeErrorLo superficial se nota enseguida: si RUTAS tuviera un objeto anidado, ese objeto seguiría siendo editable. Para congelar en serio hay que recorrer y congelar cada nivel.
| Método | Versión | Parámetros | Devuelve |
|---|---|---|---|
Object.is(a, b) | ES2015 | a, b: cualquier valor | boolean |
Object.groupBy(items, callback) | ES2024 | items: iterable, callback: (elemento, indice) que devuelve la clave | un object sin prototipo, con arrays por clave |
Object.is es igual al === salvo en dos casos que existen para eso: distingue 0 de -0, y considera que NaN es igual a NaN.
Object.groupBy es el más nuevo de toda la lista y me resolvió justo lo que hace el listado por tags de este blog:
const posts = [
{ titulo: "Objetos literales", tag: "javascript" },
{ titulo: "Migré a Next.js", tag: "next" },
{ titulo: "Métodos de Object", tag: "javascript" },
];
const porTag = Object.groupBy(posts, (post) => post.tag);
// { javascript: [ {...}, {...} ], next: [ {...} ] }Devuelve un objeto sin prototipo, así que podés usar cualquier tag como clave sin miedo a chocar con toString. Eso sí: es de 2024, conviene revisar el soporte del entorno donde va a correr.
Estos no se llaman desde Object sino desde el objeto mismo, porque los hereda de Object.prototype.
| Miembro | Versión | Qué es | Devuelve |
|---|---|---|---|
obj.toString() | ES1 | método | string, por defecto "[object Object]" |
obj.valueOf() | ES1 | método | el valor primitivo del objeto |
obj.toLocaleString() | ES3 | método | string según la configuración regional |
obj.constructor | ES1 | propiedad | la función que lo creó |
obj.__proto__ | ES2015 (Anexo B) | accesor desaconsejado | el prototipo; usá getPrototypeOf |
El truco que sigue vigente es usar el toString original para saber qué es un valor de verdad, algo que typeof no resuelve:
typeof []; // "object"
typeof null; // "object"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(new Date()); // "[object Date]"Si tuviera que quedarme con cinco, serían Object.keys, Object.entries, Object.fromEntries, el spread y Object.hasOwn. Con eso se resuelve el noventa por ciento del trabajo real con objetos.
El resto —descriptores, freeze, setPrototypeOf— aparece poco, pero cuando aparece es porque estás tocando algo importante, y ahí conviene saber que existe.
La lista es larga, pero el patrón es corto:
Objectcasi siempre recibe el objeto como primer parámetro y devuelve algo nuevo o el mismo objeto modificado. Saber cuál de las dos cosas hace cada método es la mitad de los bugs que te vas a ahorrar.
Si estás arrancando, no intentes memorizarlos. Guardate esta página, usá los cinco de arriba hasta que te salgan solos, y bajá a los otros el día que un problema te obligue.