Una de las características más remarcables de JavaScript es su flexibilidad al momento de manipular variables y objetos. Si bien esta característica puede ser beneficiosa para tus proyectos, puede traer algunos problemas si se usa de una manera incorrecta. En este artículo definiremos el concepto de mutabilidad de objetos, verás cuáles son los recaudos que hay que tener al momento de trabajar con objetos y porque, en general, habría que evitar mutar tus objetos.
Tipos de Datos
En JavaScript existen dos grandes tipos de datos: Los “primitivos” y los “no primitivos” o “de referencia”. Los tipos primitivos son string, number, boolean, null, undefined, Symbol y bigint. Los no primitivos son, entre otros, objetos y arreglos (arrays).
Los tipos primitivos son inmutables. Una vez que se le asigna un valor primitivo a una variable, la única manera de cambiarlo es asignándole otro valor. Al hacer esto, se está creando una nueva instancia de la variable.
Las cadenas de texto, por poner un ejemplo, son siempre inmutables:
Si quieres modificar una cadena de texto, en realidad tienes que crear una nueva instancia:
En cambio, los tipos de referencia son mutables. Esto quiere decir que se puede alterar el estado y las propiedades de un objeto en JavaScript sin la necesidad de tener que crear una nueva instancia del mismo.
Mutabilidad
Una mutación es una variación en la estructura de algo o alguien. Si se habla de genética, por ejemplo, una mutación se da cuando se produce un cambio en el ADN de un ser vivo, lo que produce una variación en las características de este. En la programación, y en JavaScript en particular, también se maneja el concepto de mutación o mutabilidad.
Hablamos de mutabilidad en JavaScript (o en otros lenguajes de programación) como la capacidad de un elemento de cambiar su estado. Cambiando el valor de la propiedad de un objeto, o la referencia que tiene una variable, estás haciendo uso de la mutabilidad de estos elementos.
Los objetos, arreglos y otras estructuras no primitivas son mutables. Los objetos son de tipo de referencia, lo que significa que a una variable a la que se le asigna un objeto, en realidad se le está definiendo una referencia a ese objeto. Si a otra variable se le asigna una referencia al mismo objeto y lo modifica, afectará a todas las variables vinculadas:
Al momento de crear un nuevo objeto, se crea el espacio en memoria suficiente para alojarlo y administrarlo. Si a una variable se le asigna ese objeto, se crea una referencia al espacio de memoria donde ese objeto está alocado. No importan las propiedades o valores del objeto, siempre que se cree uno nuevo ocupará otro espacio de memoria y se tratará de un nuevo objeto. Este es el motivo por el cual al comparar dos objetos distintos, incluso si tienen los mismos valores, siempre será false
:
Si bien esta característica puede parecer ventajosa en ciertos escenarios, tienes que tener cuidado.
¿Por qué importa la inmutabilidad?
La mutabilidad es una de las principales causas de que el código de las aplicaciones genere efectos secundarios. Cuando varias partes de un proyecto aplican cambios sobre un mismo dato, es muy probable que se produzcan errores. Puede darse que cierta parte del código de una aplicación esté esperando ciertos datos y, porque otra parte del código los cambió, se encuentra con unos diferentes, haciendo que los resultados de las operaciones no sean los esperados. Trabajar con objetos mutables puede hacer que cambies o rompas algo sin saberlo.
También, trabajar con entidades mutables puede causar que la depuración de tu código sea más compleja en tiempo de ejecución, y más difícil de leer y entender. Al analizar un error hay que revisar el código para determinar que es lo que se cambió inesperadamente, que contenido tienen las variables en cada momento de la ejecución, y donde se realizaron los cambios de estado o de valores.
Lo que se busca al almacenar los datos de manera inmutable es tener persistencia en ellos. La idea es saber que ha sucedido con los datos a lo largo del tiempo. Esto no significa que no haya que aplicar cambios en los objetos, sino que cualquier modificación debería reflejarse creando nuevas versiones de los objetos con los cambios aplicados, y mantener los originales sin cambios.
Como vimos anteriormente, cada vez que se crea un nuevo objeto, un nuevo espacio de memoria es asignado. Si quieres mantener las versiones originales de los objetos sin alteraciones, y creas nuevos objetos con los cambios deseados, estarías duplicando el espacio de memoria utilizado por tu aplicación. Eso es algo que claramente no quieres que suceda.
Entonces, ¿Cómo hacer para forzar la inmutabilidad?
Como mantener la inmutabilidad
JavaScript ofrece distintas maneras de asegurar la inmutabilidad de los objetos de tu código.
Object.assign
El método Object.assign
te permite crear un nuevo objeto copiando los valores de otro (u otros) objetos pasados como parámetros.
También puedes combinar varios objetos en uno solo.
Ten en cuenta que siempre el primer parámetro de Object.assign
es un objeto vacío, porque los cambios se aplican sobre este y no quieres modificar los objetos pasados como parámetro.
Object.freeze()
El método Object.freeze()
“congela” un objeto, impidiendo que sea cambiado.
Sintaxis “spread”
Usando …
puedes obtener un resultados similar al que tienes al usar Object.assign
Librerías
Existen distintas librerías para manejar inmutabilidad en JavaScript. Algunos ejemplos son:
- Immutable.js: Desarrollada por Facebook, ofrece formas de manejar objetos inmutables de manera optimizada.
- Ramda: Ofrece distintas herramientas para aplicar el paradigma funcional a tu código JavaScript.
Desafío
A lo largo del artículo dijimos que la mutabilidad en JavaScript se da, por ejemplo, en arreglos y objetos.
Entre los métodos que tenemos disponibles para trabajar con arreglos, algunos de ellos son métodos mutables: Provocan una alteración sobre el arreglo en el cual se ejecutan.
¿Cómo desarrollarías las funciones push
y pop
de manera que no modifiquen al arreglo original?
Conclusión
Los desarrolladores, a veces, suelen hacer las cosas de manera automática sin analizar las consecuencias de las funciones que crean y ejecutan en sus proyectos JavaScript. Efectuar la mutabilidad de objetos de manera errónea puede hacer que nuestras aplicaciones devuelvan resultados incorrectos, o incluso no lleguen a funcionar de manera adecuada. En este artículo definimos “mutabilidad” e “inmutabilidad” de objetos en JavaScript. Viendo algunos ejemplos, pudimos entender las consecuencias de aplicar la mutabilidad y analizar cómo y porque deberíamos evitarla.