Object.freeze(), Object.seal() y Object.preventExtensions(): Control de Inmutabilidad
Aprende a controlar la mutabilidad de objetos en JavaScript usando freeze, seal y preventExtensions para escribir código más predecible y seguro.
TL;DR - Resumen rápido
- Object.preventExtensions() impide agregar nuevas propiedades pero permite modificar/eliminar existentes
- Object.seal() impide agregar o eliminar propiedades, pero permite modificar valores existentes
- Object.freeze() hace el objeto completamente inmutable: no se puede agregar, eliminar ni modificar
- Estos métodos solo afectan el primer nivel del objeto, no objetos anidados (shallow)
- En modo estricto, violar estas restricciones lanza errores; en modo normal, falla silenciosamente
- Útiles para configuraciones, constantes y prevenir mutaciones accidentales en código
Introducción: Control de Inmutabilidad de Objetos
JavaScript es un lenguaje que permite mutar objetos libremente por defecto. Aunque esto proporciona flexibilidad, también puede conducir a bugs difíciles de rastrear cuando objetos se modifican inesperadamente. Object.freeze(), Object.seal() y Object.preventExtensions() son métodos que permiten controlar el nivel de inmutabilidad de un objeto, restringiendo qué operaciones están permitidas.
Estos tres métodos forman una jerarquía de restricciones crecientes: preventExtensions es el menos restrictivo (solo impide agregar propiedades), seal es más restrictivo (impide agregar y eliminar), y freeze es el más restrictivo (inmutabilidad completa). Cada uno retorna el objeto modificado y el cambio es permanente e irreversible.
Inmutabilidad superficial (shallow)
Es crucial entender que estos métodos solo aplican al primer nivel del objeto. Si tu objeto contiene objetos anidados, esos objetos internos siguen siendo mutables a menos que también los congeles explícitamente. Esto se conoce como inmutabilidad superficial o shallow.
Object.preventExtensions(): Evitar Nuevas Propiedades
Object.preventExtensions() es el método menos restrictivo de los tres. Previene que se agreguen nuevas propiedades al objeto, pero aún permite modificar o eliminar propiedades existentes. Es útil cuando quieres garantizar que un objeto mantiene su estructura actual sin expandirse.
Una vez aplicado preventExtensions(), el objeto no acepta nuevas propiedades. Los intentos de agregar propiedades fallan silenciosamente en modo normal, pero lanzan TypeError en modo estricto. Sin embargo, puedes modificar valores de propiedades existentes y eliminarlas con el operador delete. Este método es útil para objetos de configuración donde conoces todas las opciones por adelantado.
Verificación con Object.isExtensible()
Puedes verificar si un objeto acepta nuevas propiedades usando Object.isExtensible(obj). Retorna true si puedes agregar propiedades, false si el objeto es no-extensible. Por defecto, todos los objetos son extensibles hasta que aplicas preventExtensions(), seal() o freeze().
Object.seal(): Sellar la Estructura del Objeto
Object.seal() va un paso más allá que preventExtensions(). No solo previene agregar nuevas propiedades, sino que también marca todas las propiedades existentes como no configurables, lo que impide eliminarlas. Sin embargo, aún puedes modificar los valores de las propiedades existentes que sean escribibles.
Un objeto sellado mantiene su estructura fija: las propiedades que tiene al momento de sellarse son las únicas que tendrá siempre. No puedes agregar ni eliminar propiedades, pero puedes cambiar sus valores. Esto es ideal para objetos que representan entidades con campos conocidos que deben ser modificables pero cuya estructura debe permanecer constante, como registros de base de datos.
- No se pueden agregar nuevas propiedades al objeto
- No se pueden eliminar propiedades existentes
- Sí se pueden modificar valores de propiedades existentes
- Las propiedades se marcan como no configurables (configurable: false)
Object.freeze(): Inmutabilidad Completa
Object.freeze() es el método más restrictivo y proporciona inmutabilidad completa a nivel superficial. Un objeto congelado no puede ser modificado de ninguna manera: no puedes agregar propiedades, eliminar propiedades existentes, ni cambiar los valores de propiedades existentes. Es el equivalente a una constante verdadera en JavaScript.
Un objeto congelado es completamente inmutable en su primer nivel. Todas las propiedades se marcan como no configurables y no escribibles. Freeze es perfecto para constantes globales, configuraciones que nunca deben cambiar, o cuando trabajas con programación funcional y necesitas garantizar inmutabilidad. Sin embargo, recuerda que freeze es superficial: objetos anidados siguen siendo mutables.
Beneficios de usar freeze
Object.freeze() ofrece beneficios significativos: previene bugs por mutaciones accidentales, hace el código más predecible, facilita el razonamiento sobre el estado, y en algunos motores JavaScript puede permitir optimizaciones de rendimiento al garantizar que el objeto nunca cambiará.
Comparación entre los Tres Métodos
Los tres métodos forman una jerarquía de restricciones crecientes. Entender sus diferencias es clave para elegir el nivel de inmutabilidad adecuado para cada caso de uso. La siguiente tabla resume qué operaciones permite cada método.
Como muestra el ejemplo, preventExtensions solo bloquea agregar nuevas propiedades. Seal bloquea agregar y eliminar, pero permite modificar. Freeze bloquea todo. La elección depende de tus necesidades: usa preventExtensions para estructuras semi-flexibles, seal para esquemas fijos con datos mutables, y freeze para constantes verdaderas.
- <strong>preventExtensions</strong>: Agregar ❌ | Modificar ✅ | Eliminar ✅
- <strong>seal</strong>: Agregar ❌ | Modificar ✅ | Eliminar ❌
- <strong>freeze</strong>: Agregar ❌ | Modificar ❌ | Eliminar ❌
Métodos de Verificación de Estado
JavaScript proporciona tres métodos complementarios para verificar el estado de inmutabilidad de un objeto: Object.isExtensible(), Object.isSealed() y Object.isFrozen(). Estos permiten detectar qué restricciones se han aplicado a un objeto antes de intentar modificarlo.
Estos métodos de verificación son útiles en código defensivo, especialmente cuando recibes objetos de fuentes externas. Object.isExtensible() verifica si puedes agregar propiedades, isSealed() verifica si la estructura está fija, e isFrozen() verifica inmutabilidad completa. Nota que freeze implica seal, y seal implica no-extensible, creando una jerarquía de estados.
Jerarquía de estados
Un objeto frozen siempre está sealed, y un objeto sealed siempre es non-extensible. Pero lo contrario no es cierto: un objeto non-extensible no está necesariamente sealed, y uno sealed no está necesariamente frozen. Los métodos de verificación reflejan esta jerarquía.
Inmutabilidad Profunda (Deep Freeze)
El problema principal de Object.freeze() es que solo congela el primer nivel del objeto. Los objetos anidados siguen siendo mutables, lo que puede causar bugs sutiles. Para lograr inmutabilidad completa, necesitas congelar recursivamente todos los objetos anidados, un patrón conocido como deep freeze.
La función deepFreeze() recorre recursivamente todas las propiedades del objeto y congela cada objeto anidado que encuentra. Esto garantiza inmutabilidad verdadera en toda la estructura de datos. Es importante notar que deepFreeze debe manejar referencias circulares en producción (usando un WeakSet para rastrear objetos ya visitados) para evitar bucles infinitos.
Costo de rendimiento de deep freeze
Congelar profundamente objetos grandes puede ser costoso en rendimiento, ya que requiere recorrer toda la estructura. Usa deep freeze solo cuando realmente necesites inmutabilidad garantizada en toda la estructura, como constantes de configuración o datos de referencia.
Casos de Uso Prácticos
Estos métodos no son solo teóricos, tienen aplicaciones prácticas reales en desarrollo de aplicaciones JavaScript. Elegir el método correcto para cada situación mejora la robustez y mantenibilidad del código.
Los casos de uso más comunes incluyen: freeze para configuraciones inmutables y constantes globales, seal para esquemas de datos con estructura fija pero valores mutables (como modelos de base de datos), y preventExtensions para objetos con API pública conocida donde quieres prevenir la adición de propiedades no estándar. En frameworks y librerías, freeze es común para exponer APIs públicas inmutables.
Comportamiento en Modo Estricto vs Normal
El comportamiento de estos métodos cambia significativamente dependiendo de si estás en modo estricto ('use strict') o modo normal. En modo estricto, violar restricciones lanza errores explícitos, mientras que en modo normal las operaciones fallan silenciosamente, lo que puede ocultar bugs.
En modo estricto, cualquier intento de violar las restricciones de freeze, seal o preventExtensions lanza un TypeError, lo que facilita detectar errores durante el desarrollo. En modo normal, las operaciones simplemente no tienen efecto, lo que puede hacer que bugs pasen desapercibidos. Por esta razón, se recomienda siempre usar modo estricto cuando trabajas con objetos inmutables.
Modo estricto recomendado
Siempre usa 'use strict' cuando trabajes con freeze, seal o preventExtensions. Los errores explícitos que lanza el modo estricto te ayudarán a detectar violaciones de inmutabilidad inmediatamente, en lugar de depender de fallos silenciosos que son difíciles de debuggear.
Limitaciones y Alternativas Modernas
Aunque estos métodos son útiles, tienen limitaciones importantes. La más significativa es que son superficiales (shallow), afectando solo el primer nivel. Además, no hay forma de revertir estos cambios una vez aplicados, y pueden tener implicaciones de rendimiento en objetos grandes.
Las limitaciones incluyen: inmutabilidad solo superficial, irreversibilidad (no puedes descongelar), incompatibilidad con proxies en ciertos casos, y posibles problemas de rendimiento. Para aplicaciones modernas que requieren inmutabilidad robusta, considera bibliotecas especializadas como Immer o Immutable.js, que ofrecen estructuras de datos persistentes y actualizaciones eficientes sin mutación.
Alternativas modernas
Para aplicaciones grandes con requerimientos complejos de inmutabilidad, bibliotecas como Immer ofrecen una mejor experiencia: inmutabilidad profunda por defecto, API más ergonómica, y optimizaciones de rendimiento usando structural sharing. Object.freeze() es excelente para casos simples, pero no escala bien a estructuras complejas.
Resumen: freeze, seal, preventExtensions
Conceptos principales:
- •preventExtensions bloquea agregar propiedades, permite modificar/eliminar
- •seal bloquea agregar/eliminar propiedades, permite modificar valores
- •freeze inmutabilidad completa: no agregar, modificar ni eliminar
- •Todos son superficiales (shallow): objetos anidados siguen mutables
- •En modo estricto lanzan TypeError, en modo normal fallan silenciosamente
- •Irreversibles: una vez aplicados no se pueden deshacer
Mejores prácticas:
- •Usar freeze para constantes y configuraciones inmutables
- •Usar seal para esquemas de datos con estructura fija
- •Implementar deepFreeze para inmutabilidad profunda si es necesario
- •Siempre trabajar en modo estricto para detectar violaciones
- •Verificar estado con isExtensible/isSealed/isFrozen antes de modificar
- •Considerar bibliotecas como Immer para casos complejos