Descriptores de propiedades en JavaScript
Aprende cómo funcionan los flags de propiedad (writable, enumerable, configurable) y los descriptores en JavaScript, incluyendo defineProperty, freeze, seal y modo estricto.
Los flags y descriptores de propiedades de JavaScript ofrecen un control preciso sobre las propiedades de los objetos, lo que permite desarrollar aplicaciones robustas y seguras. Este artículo explora estas características en detalle, con ejemplos prácticos de código para ayudarte a gestionar el comportamiento de las propiedades de forma efectiva.
Entendiendo los atributos de propiedades en JavaScript
Los objetos de JavaScript son colecciones de propiedades, y cada propiedad tiene atributos asociados que definen su comportamiento. Estos atributos, comúnmente llamados flags de propiedad, incluyen:
- Writable: Determina si el valor de la propiedad puede cambiarse.
- Enumerable: Controla si la propiedad es visible durante la enumeración, como en un bucle
for...in. - Configurable: Especifica si la propiedad puede eliminarse o modificarse.
Estos flags son fundamentales para controlar el acceso a las propiedades de los objetos, garantizar la integridad de los datos e implementar encapsulación en aplicaciones JavaScript.
Explorando los descriptores de propiedades
Los descriptores de propiedades proporcionan información detallada sobre la propiedad de un objeto, encapsulando su valor y sus flags. Se recuperan con Object.getOwnPropertyDescriptor(obj, propName) y se establecen con Object.defineProperty(obj, propName, descriptor). Un objeto descriptor de propiedad puede contener:
value: El valor asociado a la propiedad.writable: Indica si el valor de la propiedad puede cambiarse.enumerable: Indica si la propiedad es enumerable.configurable: Determina si el descriptor de la propiedad puede modificarse y si la propiedad puede eliminarse del objeto.
Nota: Cuando creas una propiedad de la forma habitual (user.name = "John"), los tres flags se establecen en true. Pero al definir una propiedad nueva mediante Object.defineProperty, cualquier flag no especificado tiene como valor predeterminado false.
Object.getOwnPropertyDescriptor solo examina las propiedades propias del objeto. Si solicitas una propiedad que el objeto hereda de su prototipo (o una propiedad que no existe), devuelve undefined.
Para saber más sobre cómo los objetos heredan propiedades, consulta Herencia prototípica.
Descriptores de datos vs. descriptores de acceso
Hasta ahora hemos descrito los descriptores de datos, que almacenan un value junto con el flag writable. JavaScript también admite descriptores de acceso, que sustituyen value/writable por funciones getter y setter:
get: una función que se llama cuando se lee la propiedad (no recibe argumentos).set: una función que se llama cuando se asigna un valor a la propiedad (recibe el nuevo valor).
Un descriptor es o bien un descriptor de datos o bien un descriptor de acceso — nunca ambos. Combinar value/writable con get/set lanza un error. Ambos tipos comparten los flags enumerable y configurable.
Las propiedades de acceso permiten calcular un valor al leer o validarlo al escribir. En el siguiente ejemplo, exponemos un accessor fullName respaldado por dos propiedades de datos:
Para un tratamiento más completo de la sintaxis get/set (incluyendo la forma abreviada dentro de literales de objeto), consulta Getters y setters de propiedades. Dado que los getters y setters se ejecutan con this enlazado al objeto, también es útil entender Métodos de objeto y "this".
Definir y leer múltiples propiedades a la vez
Para trabajar con varias propiedades en un solo paso, JavaScript proporciona los equivalentes plurales de los métodos anteriores:
Object.defineProperties(obj, descriptors)define múltiples propiedades a partir de un mapa de descriptores.Object.getOwnPropertyDescriptors(obj)devuelve los descriptores de todas las propiedades propias (incluidas las no enumerables y las claves symbol) como un único object.
Object.getOwnPropertyDescriptors es especialmente útil para clonar un objeto con sus flags — un spread simple o Object.assign copia los valores pero restablece todos los flags a true y omite los accessors.
Manipulación de flags de propiedades
Comprender y manipular los flags de propiedades es fundamental para un desarrollo efectivo en JavaScript. Veamos cómo controlar estos flags para ajustar el comportamiento de las propiedades.
Hacer una propiedad no escribible
Impedir modificaciones en una propiedad garantiza la coherencia de los datos. Esto se consigue estableciendo el flag writable en false.
El comportamiento de la asignación fallida depende del modo en que se ejecute el código. En modo no estricto, escribir en una propiedad no escribible falla silenciosamente: la asignación simplemente se ignora, no se lanza ningún error y la ejecución continúa — lo que puede ocultar errores. En modo estricto ("use strict", y el valor predeterminado dentro de módulos ES y cuerpos de clase), la misma asignación lanza un TypeError. Esta regla se aplica a cualquier operación que viole un flag: eliminar una propiedad no configurable o agregar una propiedad a un objeto no extensible también falla silenciosamente en modo no estricto y lanza una excepción en modo estricto.
Ocultar una propiedad de la enumeración
A veces es necesario ocultar propiedades de los procesos de enumeración, como los bucles for...in. Esto se consigue estableciendo el flag enumerable en false.
Evitar la eliminación y modificación de propiedades
Para garantizar que una propiedad siga siendo una parte constante de un objeto, establece el flag configurable en false.
Marcar una propiedad como no configurable es una operación irreversible — no existe ningún flag para volver a hacerla configurable, y ya no es posible cambiar enumerable ni convertir la propiedad entre un descriptor de datos y uno de acceso.
No obstante, hay dos excepciones importantes cuando una propiedad es no configurable:
- Puedes cambiar
writabledetrueafalse(pero no defalseatrue). - Si la propiedad sigue siendo
writable: true, puedes cambiar suvalue— ya sea mediante asignación directa o medianteObject.defineProperty.
En otras palabras, configurable: false bloquea la forma de la propiedad, pero no necesariamente su valor. Para congelar verdaderamente el valor de una propiedad, establece tanto configurable: false como writable: false.
APIs de alto nivel basadas en estos flags
Raramente necesitas establecer flags propiedad por propiedad. JavaScript incluye tres métodos integrados que modifican estos flags en todo un objeto:
Object.preventExtensions(obj)— impide que se añadan nuevas propiedades. Las propiedades existentes aún pueden cambiarse o eliminarse.Object.seal(obj)— impide añadir y eliminar propiedades marcando todas las propiedades existentes comoconfigurable: false. Los valores aún pueden cambiar.Object.freeze(obj)— el más estricto: sella el objeto y establece todas las propiedades comowritable: false, de modo que nada puede añadirse, eliminarse ni modificarse.
Cada método tiene una comprobación equivalente: Object.isExtensible, Object.isSealed y Object.isFrozen. Ten en cuenta que operan a un solo nivel — Object.freeze no congela los objetos anidados (es un congelamiento "superficial").
Conclusión
Los flags y descriptores de propiedades te ofrecen un control preciso sobre el comportamiento de las propiedades de los objetos:
- Un descriptor de datos combina un
valueconwritable; un descriptor de acceso usa funcionesget/seten su lugar. Ambos compartenenumerableyconfigurable. - Lee los flags con
Object.getOwnPropertyDescriptor(una propiedad) oObject.getOwnPropertyDescriptors(todas las propiedades propias); escríbelos conObject.definePropertyoObject.defineProperties. Las propiedades heredadas o inexistentes devuelvenundefined. configurable: falsees irreversible y bloquea la forma de la propiedad, aunque una propiedad que siga siendowritablepuede tener su valor modificado y su flagwritabledesactivado.- Violar un flag falla silenciosamente en modo no estricto, pero lanza un
TypeErroren modo estricto. - Usa
Object.freeze,Object.sealyObject.preventExtensionscuando quieras bloquear un objeto completo en lugar de flags individuales.
Próximos pasos: explora Getters y setters de propiedades para conocer la sintaxis de los accessors, Métodos de objeto y "this" para entender cómo se comporta this dentro de ellos, y Herencia prototípica para ver cómo la búsqueda de propiedades recorre la cadena de prototipos.