Object.freeze y Object.seal en JavaScript | Nicolás Garzón
JavaScript permite restringir cambios estructurales sobre un objeto mediante tres niveles:
JavaScript
Copiar Object. preventExtensions ( object) ;
Object. seal ( object) ;
Object. freeze ( object) ; De menor a mayor restricción:
Texto
Copiar preventExtensions → seal → freezeUn objeto normal permite agregar propiedades.
JavaScript
Copiar const settings = {
theme : "dark" ,
} ;
settings. pageSize = 20 ; JavaScript
Copiar Object. isExtensible ( settings) ; JavaScript
Copiar Object. preventExtensions ( settings) ;
No pueden agregarse propiedades nuevas.
Las propiedades existentes pueden cambiar si son writable.
Las propiedades existentes pueden eliminarse si son configurable.
Sus descriptores todavía pueden modificarse dentro de los límites permitidos.
JavaScript
Copiar settings. theme = "light" ;
delete settings. theme;
settings. language = "es" ; En modo estricto, agregar una propiedad produce un TypeError. Fuera de modo estricto puede fallar silenciosamente.
JavaScript
Copiar Object. isExtensible ( settings) ; JavaScript
Copiar const settings = {
theme : "dark" ,
} ;
Object. seal ( settings) ;
No permite agregar propiedades.
No permite eliminar propiedades.
Hace no configurables sus propiedades propias.
Permite cambiar el valor de propiedades de datos que continúan siendo writable.
JavaScript
Copiar settings. theme = "light" ;
settings. language = "es" ;
delete settings. theme; JavaScript
Copiar Object. isSealed ( settings) ;
Object. isExtensible ( settings) ; JavaScript
Copiar const settings = {
theme : "dark" ,
} ;
Object. freeze ( settings) ;
No permite agregar propiedades.
No permite eliminarlas.
Hace no configurables sus propiedades propias.
Hace no writable sus propiedades de datos.
JavaScript
Copiar settings. theme = "light" ;
settings. language = "es" ;
delete settings. theme; JavaScript
Copiar Object. isFrozen ( settings) ;
Object. isSealed ( settings) ;
Object. isExtensible ( settings) ; Todo objeto congelado también está sellado y no es extensible.
JavaScript
Copiar Object. getOwnPropertyDescriptor (
settings,
"theme" ,
) ; Una propiedad normal suele ser:
Texto
Copiar writable: true
enumerable: true
configurable: trueDespués de Object.freeze:
Texto
Copiar writable: false
enumerable: true
configurable: falseenumerable no cambia automáticamente.
JavaScript
Copiar const settings = Object. freeze ( {
theme : "dark" ,
} ) ;
settings. theme = "light" ; Fuera de modo estricto, la asignación puede fallar sin lanzar.
JavaScript
Copiar "use strict" ;
settings. theme = "light" ; Los módulos y las clases son estrictos automáticamente.
JavaScript
Copiar const configuration = Object. freeze ( {
database : {
host : "localhost" ,
} ,
} ) ; No puede reemplazarse database:
JavaScript
Copiar configuration. database = { } ; Pero el objeto interior no está congelado.
JavaScript
Copiar configuration. database. host = "production-db" ; JavaScript
Copiar Object. isFrozen ( configuration) ;
Object. isFrozen ( configuration. database) ; JavaScript
Copiar const values = Object. freeze ( [ 1 , 2 , 3 ] ) ; No puede cambiarse una posición ni la longitud.
JavaScript
Copiar values[ 0 ] = 10 ;
values. push ( 4 ) ;
values. pop ( ) ; Los métodos mutables fallan o lanzan, especialmente en modo estricto.
Los elementos que son objetos continúan siendo mutables si no están congelados.
JavaScript
Copiar const products = Object. freeze ( [
{ name : "Keyboard" } ,
] ) ;
products[ 0 ] . name = "Mouse" ; JavaScript
Copiar let internalValue = 0 ;
const object = {
get value ( ) {
return internalValue;
} ,
set value ( nextValue ) {
internalValue = nextValue;
} ,
} ;
Object. freeze ( object) ;
object. value = 10 ; freeze hace no configurable el accessor, pero no elimina su setter. El setter puede continuar modificando estado externo.
Por eso un objeto congelado no garantiza que todas sus observaciones futuras sean idénticas.
JavaScript
Copiar class Counter {
#value = 0 ;
increment ( ) {
this . #value++ ;
}
read ( ) {
return this . #value;
}
}
const counter = Object. freeze (
new Counter ( ) ,
) ;
counter. increment ( ) ;
counter. read ( ) ; Los campos privados no son propiedades normales del objeto y no son congelados por Object.freeze.
Congelar la superficie pública no detiene necesariamente el estado interno de una clase.
JavaScript
Copiar const map = Object. freeze (
new Map ( ) ,
) ;
map. set ( "key" , "value" ) ; Object.freeze restringe propiedades propias normales, pero no congela el contenido almacenado en los slots internos de Map o Set.
JavaScript
Copiar const set = Object. freeze ( new Set ( ) ) ;
set. add ( "value" ) ; Para exponer una colección de solo lectura necesitas controlar su API, retornar copias o utilizar una abstracción que no permita mutación.
Puede crearse una función que recorra valores anidados.
JavaScript
Copiar function deepFreeze ( value, visited = new WeakSet ( ) ) {
if (
value === null ||
( typeof value !== "object" &&
typeof value !== "function" )
) {
return value;
}
if ( visited. has ( value) ) {
return value;
}
visited. add ( value) ;
for ( const key of Reflect. ownKeys ( value) ) {
deepFreeze ( value[ key] , visited) ;
}
return Object. freeze ( value) ;
} WeakSet evita ciclos infinitos.
Sin embargo, esta función tampoco convierte automáticamente en inmutables:
Campos privados.
Contenido interno de Map y Set.
Recursos del entorno.
Estado cerrado en closures.
Operaciones expuestas por accessors.
“Deep freeze” debe definirse según los tipos reales del dominio.
Las funciones también son objetos.
JavaScript
Copiar function calculate ( ) { }
Object. freeze ( calculate) ; Esto restringe sus propiedades propias, pero no cambia el código de su cuerpo ni congela automáticamente los objetos que utiliza mediante closures.
JavaScript
Copiar const state = Object. seal ( {
status : "pending" ,
error : null ,
} ) ; El conjunto de propiedades permanece estable, pero sus valores pueden actualizarse.
Puede ser útil cuando la estructura está definida y el estado necesita evolucionar.
Aun así, un schema o TypeScript comunican mejor la forma durante desarrollo. seal es una restricción en tiempo de ejecución.
JavaScript
Copiar const configuration = Object. freeze ( {
retryCount : 3 ,
timeout : 5000 ,
} ) ; Puede proteger una configuración plana contra mutaciones accidentales.
JavaScript
Copiar const configuration = deepFreeze ( {
api : {
timeout : 5000 ,
} ,
} ) ; Solo úsalo cuando el costo y los tipos involucrados sean comprendidos.
Cifra información.
Impide que se lea.
Valida datos.
Protege contra código que conserva otras referencias a objetos anidados.
Congela estado privado o de closures.
Vuelve inmutable un backend o una fuente externa.
Sustituye autorización.
Es una herramienta de integridad estructural, principalmente contra cambios accidentales.
JavaScript
Copiar Object. freeze ( state) ; Restringe un objeto existente.
JavaScript
Copiar const nextState = {
... state,
status : "completed" ,
} ; Representa una transición mediante otro valor.
Puedes combinar ambas ideas, pero resuelven problemas distintos.
Congelar cada estado de una aplicación puede añadir costo. En muchos casos basta con no mutar referencias compartidas y utilizar herramientas de desarrollo que detecten errores.
Operación Agregar Eliminar Cambiar valor writable Reconfigurar Objeto normal Sí Sí Sí Sí `preventExtensions` No Sí Sí Dentro de límites `seal` No No Sí No `freeze` No No No No
JavaScript
Copiar function createPublicConfiguration ( input ) {
const configuration = {
environment : input. environment,
apiUrl : input. apiUrl,
features : {
reports : Boolean (
input. features?. reports,
) ,
} ,
} ;
return deepFreeze ( configuration) ;
} La función construye una estructura conocida y evita que sus ramas normales cambien accidentalmente después.
Pensar que freeze crea una copia.
Esperar congelación profunda automática.
Creer que un array congelado congela sus objetos interiores.
Suponer que Map y Set dejan de aceptar entradas.
Pensar que los campos privados quedan congelados.
Confundir seal con valores completamente inmutables.
Ignorar diferencias entre modo estricto y no estricto.
Usar deep freeze sobre cualquier valor sin considerar ciclos y tipos especiales.
Presentar freeze como medida de seguridad.
Congelar estructuras grandes en cada actualización sin medir el costo.
preventExtensions impide agregar propiedades.
seal también impide eliminar o reconfigurar.
freeze además vuelve no writable las propiedades de datos.
Las tres operaciones afectan al mismo objeto.
Las restricciones son superficiales.
Accessors, campos privados, closures, Maps y Sets pueden conservar estado mutable.
Object.isExtensible, isSealed e isFrozen comprueban cada nivel.
Freeze e inmutabilidad por diseño no son lo mismo.
Estas APIs protegen principalmente contra mutación accidental.
¿Qué cambio sigue siendo posible?
JavaScript
Copiar const configuration = Object. freeze ( {
api : {
timeout : 5000 ,
} ,
} ) ; Respuesta Puede cambiarse configuration.api.timeout porque api es otro objeto y no fue congelado. No puede reemplazarse configuration.api en el objeto exterior congelado.
Factory functions muestra otra estrategia para crear objetos, validar datos, inyectar dependencias y encapsular estado mediante closures.