La inmutabilidad es un principio fundamental en la programación funcional, y también tiene mucho que ofrecer a los programas orientados a objetos. Un objeto mutable es un objeto cuyo estado se puede modificar después de su creación. Un objeto inmutable es un objeto cuyo estado no se puede modificar después de su creación.
¿Cuál es un ejemplo de un objeto inmutable en JavaScript?
En JavaScript, algunos tipos incorporados (números, cadenas) son inmutables, pero los objetos personalizados son generalmente mutables.
Algunos objetos JavaScript inmutables incorporados son Math
, Date
.
Aquí hay algunas formas de agregar/simular inmutabilidad en objetos JavaScript simples.
Propiedades constantes de objetos
Al combinar writable: false
y configurable: false
, esencialmente puede crear una constante (que no se puede cambiar, redefinir o eliminar) como una propiedad de objeto, como:
let myObject = {};
Object.defineProperty(myObject, 'number', {
value: 42,
writable: false,
configurable: false,
});
console.log(myObject.number); // 42
myObject.number = 43;
console.log(myObject.number); // 42
Evitar extensiones
Si desea evitar que se agreguen nuevas propiedades a un objeto, pero por lo demás dejar las demás propiedades del objeto intactas, llame a Object.preventExtensions(...)
:
var myObject = {
a: 2,
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
En modo no estricto, la creación de b
falla silenciosamente. En modo estricto, arroja un TypeError
.
Sellado (Seal)
Object.seal()
crea un objeto "sellado", lo que significa que toma un objeto existente y esencialmente llama a Object.preventExtensions()
en él, pero también marca todas sus propiedades existentes como configurable: false
.
Así, no solo no se pueden agregar más propiedades, sino que tampoco se pueden reconfigurar ni eliminar propiedades existentes (aunque aún se pueden modificar sus valores).
Congelar (Freeze)
Object.freeze()
crea un objeto congelado, lo que significa que toma un objeto existente y esencialmente llama a Object.seal()
en él, pero también marca todas las propiedades de "accesador de datos" como writable:false
, para que sus valores no puedan modificarse.
Este enfoque es el nivel más alto de inmutabilidad que se puede lograr para un objeto en sí mismo, ya que evita cualquier cambio en el objeto o en cualquiera de sus propiedades directas (aunque, como se mencionó anteriormente, el contenido de cualquier otro objeto referenciado no se ve afectado).
var immutable = Object.freeze({});
Congelar un objeto no permite agregar nuevas propiedades a un objeto y evita eliminar o alterar las propiedades existentes. Object.freeze()
conserva la enumerabilidad, configurabilidad, escritura y el prototipo del objeto. Devuelve el objeto pasado y no crea una copia congelada.
¿Cuáles son las ventajas y desventajas de la inmutabilidad?
Ventajas
- Detección de cambios más sencilla: la igualdad de objetos se puede determinar de manera eficiente y sencilla a través de la igualdad referencial. Esto es útil para comparar diferencias de objetos en React y Redux.
- Los programas con objetos inmutables son menos complicados de entender, ya que no hay que preocuparse de cómo un objeto puede evolucionar con el tiempo.
- Las copias defensivas ya no son necesarias cuando los objetos inmutables se devuelven o se pasan a funciones, ya que no hay posibilidad de que un objeto inmutable sea modificado por ellas.
- Fácil compartición a través de referencias: una copia de un objeto es tan buena como otra, por lo que se pueden cachear objetos o reutilizar el mismo objeto varias veces.
- Seguros para hilos: los objetos inmutables se pueden usar de forma segura entre hilos en un entorno multihilo, ya que no hay riesgo de que sean modificados en otros hilos que se ejecuten simultáneamente.
- Usando librerías como ImmutableJS, los objetos se modifican usando compartición estructural y se necesita menos memoria para tener múltiples objetos con estructuras similares.
Contras
- Las implementaciones ingenuas de estructuras de datos inmutables y sus operaciones pueden resultar en un rendimiento extremadamente pobre porque se crean nuevos objetos cada vez. Se recomienda usar bibliotecas para estructuras de datos inmutables eficientes y operaciones que aprovechen la compartición estructural.
- La asignación (y desasignación) de muchos objetos pequeños en lugar de modificar los existentes puede causar un impacto en el rendimiento. La complejidad del asignador o del recolector de basura suele depender del número de objetos en el heap.
- Las estructuras de datos cíclicas como los grafos son difíciles de construir. Si tienes dos objetos que no se pueden modificar después de la inicialización, ¿cómo puedes hacer que se apunten entre sí?
¿Cómo se puede lograr la inmutabilidad en su propio código?
La alternativa es usar declaraciones const
combinadas con las técnicas mencionadas anteriormente para la creación. Para objetos "mutables", use el operador de propagación, Object.assign
, Array.concat()
, etc., para crear nuevos objetos en lugar de mutar el objeto original.
Ejemplos:
// Ejemplo de Array
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]
// Ejemplo de Objeto
const human = Object.freeze({ race: 'human' });
const john = { ...human, name: 'John' }; // {race: "human", name: "John"}
const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}