Propiedades Enumerable y Own en JavaScript: Distinguir Propias de Heredadas
Aprende a distinguir entre propiedades propias y heredadas, enumerables y no-enumerables, usando hasOwnProperty() y propertyIsEnumerable().
TL;DR - Resumen rápido
- Propiedades propias (own) pertenecen directamente al objeto, heredadas vienen del prototipo
- Propiedades enumerables aparecen en for...in y Object.keys(), no-enumerables están ocultas
- hasOwnProperty() verifica si una propiedad es propia del objeto, no heredada
- propertyIsEnumerable() verifica si una propiedad es propia Y enumerable simultáneamente
- for...in itera propias y heredadas enumerables, Object.keys() solo propias enumerables
- Object.getOwnPropertyNames() retorna todas las propias, incluyendo no-enumerables
Introducción: Tipos de Propiedades en JavaScript
En JavaScript, las propiedades de un objeto se clasifican en dos dimensiones importantes: pueden ser propias o heredadas, y pueden ser enumerables o no-enumerables. Esta distinción es fundamental para entender cómo funciona la iteración de objetos, la herencia prototípica, y métodos como Object.keys() o for...in.
Las propiedades propias (own properties) son aquellas definidas directamente en el objeto, mientras que las heredadas vienen de la cadena de prototipos. Las propiedades enumerables son visibles en loops de iteración, mientras que las no-enumerables están ocultas de la iteración pero siguen siendo accesibles directamente. Entender estas diferencias es crucial para evitar bugs sutiles al trabajar con objetos.
Por qué importa esta distinción
La distinción entre propias/heredadas y enumerables/no-enumerables afecta cómo se comportan métodos de iteración, serialización con JSON.stringify(), clonado de objetos, y la herencia prototípica. Un código que no considera estas diferencias puede iterar propiedades inesperadas o perder propiedades importantes.
Propiedades Propias (Own Properties)
Las propiedades propias son aquellas que existen directamente en el objeto, definidas mediante asignación directa, Object.defineProperty(), o durante la construcción del objeto. No vienen de la cadena de prototipos y pertenecen exclusivamente al objeto en cuestión.
En el ejemplo, las propiedades nombre y edad son propias del objeto persona porque fueron definidas directamente en él. Puedes verificar si una propiedad es propia usando hasOwnProperty(). Las propiedades propias son las que se copian al clonar un objeto con Object.assign() o el spread operator, y las que se incluyen en Object.keys(), Object.values() y Object.entries().
Propiedades Heredadas del Prototipo
Las propiedades heredadas provienen de la cadena de prototipos del objeto. En JavaScript, todos los objetos heredan de Object.prototype (salvo que se creen con Object.create(null)), y pueden heredar de prototipos personalizados definidos por constructores o clases.
Las propiedades heredadas son accesibles como si fueran propias, pero no pertenecen directamente al objeto. El operador in las detecta, pero hasOwnProperty() retorna false. El loop for...in itera tanto propias como heredadas (si son enumerables), lo que puede causar problemas si no se filtra correctamente. La mayoría de métodos modernos como Object.keys() ignoran propiedades heredadas automáticamente.
Prototipos nativos
Todos los objetos literales heredan de Object.prototype, los arrays de Array.prototype, las funciones de Function.prototype, etc. Estas propiedades heredadas incluyen métodos como toString(), hasOwnProperty(), valueOf() y muchos otros. La mayoría son no-enumerables para no contaminar la iteración.
Propiedades Enumerables vs No-Enumerables
El atributo enumerable de una propiedad determina si aparece en operaciones de enumeración como for...in, Object.keys(), Object.values() y Object.entries(). Por defecto, las propiedades creadas por asignación son enumerables, mientras que las definidas con Object.defineProperty() son no-enumerables por defecto.
Las propiedades no-enumerables son útiles para ocultar métodos auxiliares, metadatos internos o propiedades que no deben aparecer al serializar objetos. JSON.stringify() ignora propiedades no-enumerables automáticamente. La mayoría de métodos y propiedades nativas de JavaScript son no-enumerables para mantener limpia la iteración de objetos, como Array.length o Object.prototype.toString.
- Propiedades creadas por asignación (obj.x = y) son enumerables por defecto
- Propiedades con Object.defineProperty() son no-enumerables por defecto
- Propiedades no-enumerables se excluyen de for...in, Object.keys(), JSON.stringify()
- Útiles para ocultar implementación interna sin afectar acceso directo
Métodos para Detectar Tipo de Propiedades
JavaScript proporciona varios métodos para detectar si una propiedad es propia, heredada, enumerable o no-enumerable. Cada método tiene un propósito específico y retorna información diferente sobre las propiedades.
hasOwnProperty(): Verificar Propiedades Propias
El método hasOwnProperty() retorna true si la propiedad especificada es propia del objeto (no heredada). Es el método más común para filtrar propiedades heredadas al iterar con for...in.
hasOwnProperty() es fundamental al usar for...in para evitar iterar propiedades heredadas. Sin embargo, tiene un problema: si un objeto tiene una propiedad propia llamada "hasOwnProperty", el método original queda oculto. La forma más segura es usar Object.prototype.hasOwnProperty.call(obj, prop), o en entornos modernos, Object.hasOwn(obj, prop) que es más corto y seguro.
Object.hasOwn() en ES2022
ES2022 introdujo Object.hasOwn(obj, prop) como alternativa más segura a hasOwnProperty(). No depende del prototipo del objeto y funciona incluso si hasOwnProperty ha sido sobrescrito. Úsalo en lugar de hasOwnProperty() en código moderno.
propertyIsEnumerable(): Verificar Enumerable y Own
El método propertyIsEnumerable() retorna true solo si la propiedad es propia del objeto Y es enumerable. Es una verificación más estricta que hasOwnProperty() porque combina ambos criterios.
propertyIsEnumerable() es menos común que hasOwnProperty(), pero útil cuando necesitas verificar ambos atributos simultáneamente. Retorna false para propiedades heredadas (incluso si son enumerables) y para propiedades propias no-enumerables. Es equivalente a verificar hasOwnProperty() y el atributo enumerable del descriptor de la propiedad.
Object.keys(), values(), entries()
Estos métodos retornan solo propiedades propias y enumerables, ignorando tanto propiedades heredadas como propiedades no-enumerables. Son la forma moderna recomendada de iterar objetos en la mayoría de casos.
Para obtener todas las propiedades propias (incluyendo no-enumerables), usa Object.getOwnPropertyNames(). Para obtener símbolos como claves, usa Object.getOwnPropertySymbols(). Estos métodos son útiles cuando necesitas acceso completo a todas las propiedades del objeto, independientemente de su configuración de enumerabilidad.
Comparación: for...in vs Object.keys()
La diferencia fundamental entre for...in y Object.keys() es que for...in itera propiedades propias y heredadas (si son enumerables), mientras que Object.keys() solo itera propiedades propias enumerables. Esta diferencia puede causar bugs si no se entiende correctamente.
En código moderno, prefiere Object.keys(), Object.values() o Object.entries() sobre for...in porque son más predecibles y no requieren verificación con hasOwnProperty(). for...in solo es necesario cuando específicamente necesitas iterar propiedades heredadas, o cuando trabajas con código legacy. Además, los métodos Object retornan arrays, permitiendo usar métodos funcionales como map() y filter() directamente.
- <strong>for...in</strong>: Itera propias y heredadas enumerables, requiere hasOwnProperty()
- <strong>Object.keys()</strong>: Solo propias enumerables, retorna array
- <strong>Object.getOwnPropertyNames()</strong>: Todas las propias (enumerables y no)
- <strong>Recomendación</strong>: Usa Object.keys/values/entries por defecto
Casos de Uso Prácticos
Entender la distinción entre propias/heredadas y enumerables/no-enumerables tiene aplicaciones prácticas importantes al clonar objetos, serializar datos, iterar propiedades de forma segura, y trabajar con herencia.
Los casos de uso incluyen: filtrar propiedades al iterar con for...in usando hasOwnProperty() para evitar propiedades heredadas, clonar objetos preservando solo propiedades propias, serializar objetos excluyendo propiedades no-enumerables con JSON.stringify(), y crear objetos con métodos auxiliares ocultos usando enumerable: false. En testing y debugging, Object.getOwnPropertyNames() es útil para inspeccionar todas las propiedades incluyendo las ocultas.
Patrón Común: Iterar Solo Propiedades Propias
Un patrón muy común en JavaScript es iterar sobre propiedades propias de un objeto, excluyendo heredadas. Aunque Object.keys() lo hace automáticamente, cuando usas for...in debes aplicar este filtro manualmente.
Este patrón es tan común que muchos desarrolladores lo internalizan. Sin embargo, en código moderno es preferible usar Object.keys() directamente que usar for...in con hasOwnProperty(). Object.keys() es más legible, retorna un array permitiendo usar métodos funcionales, y tiene mejor soporte en herramientas de análisis estático. Solo usa for...in cuando realmente necesites propiedades heredadas o estés trabajando con código legacy.
Mejor práctica moderna
Prefiere Object.keys(), Object.values() o Object.entries() sobre for...in en código nuevo. Son más seguros, predecibles y expresivos. Solo usa for...in si necesitas explícitamente iterar propiedades heredadas o si mantienes código legacy.
Resumen: Propiedades Enumerable y Own
Conceptos principales:
- •Propias (own): definidas directamente, Heredadas: vienen del prototipo
- •Enumerables: visibles en for...in/Object.keys(), No-enumerables: ocultas
- •hasOwnProperty() verifica si propiedad es propia, no heredada
- •propertyIsEnumerable() verifica propia Y enumerable simultáneamente
- •for...in itera propias + heredadas, Object.keys() solo propias
- •Object.getOwnPropertyNames() incluye propias no-enumerables
Mejores prácticas:
- •Preferir Object.keys/values/entries sobre for...in por defecto
- •Usar hasOwnProperty() o Object.hasOwn() al iterar con for...in
- •Hacer propiedades auxiliares no-enumerables para APIs limpias
- •Recordar que JSON.stringify() ignora no-enumerables automáticamente
- •Usar Object.getOwnPropertyNames() para debugging completo
- •En ES2022+, usar Object.hasOwn() en lugar de hasOwnProperty()