Command Palette

Search for a command to run...

Inmutabilidad en JavaScript: Domina el Código Predecible

Aprende a usar estructuras de datos inmutables para crear código más predecible, testeable y libre de efectos secundarios.

Lectura: 13 min
Nivel: Intermedio

TL;DR - Resumen rápido

  • const crea variables inmutables que no pueden reasignarse
  • Los objetos creados con const son técnicamente inmutables en JavaScript
  • Usa Object.freeze() para prevenir mutaciones en objetos
  • Los métodos de array como map() y filter() crean arrays inmutables
  • La inmutabilidad reduce bugs causados por mutaciones inesperadas

Introducción a la Inmutabilidad

La inmutabilidad es el concepto de crear datos que no pueden cambiar después de su creación. En JavaScript, esto significa que una vez que creas un valor, ese valor permanece igual para siempre. La inmutabilidad es un concepto fundamental en la programación funcional y una práctica creciente en JavaScript moderno para crear código más predecible y libre de bugs.

JavaScript no es un lenguaje puramente funcional como Haskell o Elm, pero ofrece características para trabajar con datos inmutables. La inmutabilidad reduce la complejidad cognitiva porque no necesitas rastrear dónde y cómo se modifican los datos, y facilita el testing porque los datos inmutables son más fáciles de probar y depurar.

  • Elimina bugs causados por mutaciones inesperadas
  • Facilita el rastreo de cambios en el código
  • Mejora la predecibilidad del comportamiento del programa
  • Hace el código más fácil de probar y depurar
  • Permite optimizaciones automáticas por el motor de JavaScript

¿Qué es la Inmutabilidad?

La inmutabilidad significa que un valor, una vez creado, no puede cambiar. En JavaScript, los tipos primitivos (números, strings, booleans) son inmutables por naturaleza, mientras que los objetos y arrays son mutables por defecto. Sin embargo, podemos crear datos inmutables usando const y técnicas específicas.

Tipos Inmutables en JavaScript

En JavaScript, los valores primitivos (number, string, boolean, null, undefined, symbol) son inmutables por defecto. No puedes cambiar el valor de una variable primitiva declarada con const. Sin embargo, los objetos y arrays son mutables por defecto, incluso si se declaran con const.

tipos-inmutables.js
Loading code...

El ejemplo muestra que los primitivos son inmutables: no puedes reasignar nombre o edad después de su declaración con const. Sin embargo, los objetos y arrays son mutables por defecto: puedes agregar, eliminar o modificar propiedades de usuario y elementos de numeros, incluso aunque se declararon con const. Aunque const previene la reasignación de variables, no hace que los objetos y arrays sean inmutables.

const vs let para Inmutabilidad

Aunque const y let se usan para declarar variables, tienen propósitos diferentes en el contexto de inmutabilidad. const se usa para valores que no deben cambiar, mientras que let se usa para valores que pueden cambiar. Sin embargo, const no garantiza inmutabilidad de objetos y arrays, solo previene la reasignación de la variable.

const para Primitivos

Para valores primitivos (números, strings, booleans), const crea verdaderos valores inmutables. Una vez que declaras una variable primitiva con const, su valor no puede cambiar. Esto es ideal para constantes de configuración, valores de cálculo y cualquier dato que no debe modificarse.

const-primitivos.js
Loading code...

El ejemplo muestra que const para primitivos crea valores inmutables. PI y TAXA son constantes que no pueden cambiar, lo que es apropiado para valores de configuración. Si intentas reasignar PI o TAXA, JavaScript lanza un error en modo estricto.

let para Valores Mutables

let se usa para valores que pueden cambiar durante la ejecución del programa. Aunque puedes usar let para variables que eventualmente se volverán inmutables, la convención es usar const desde el principio y solo cambiar a let si realmente necesitas mutabilidad.

let-mutables.js
Loading code...

El ejemplo muestra cómo usar let para valores que cambian. El contador comienza en 0 y se incrementa en cada llamada. Si necesitas hacer el contador inmutable, debes crear una nueva variable en cada iteración en lugar de mutar la existente.

const no garantiza inmutabilidad de objetos

const previene la reasignación de variables, pero no hace que los objetos y arrays sean inmutables. Puedes agregar, eliminar o modificar propiedades de un objeto declarado con const. Para inmutabilidad de objetos, necesitas usar técnicas adicionales como Object.freeze() o crear copias.

Crear Objetos Inmutables

Los objetos en JavaScript son mutables por defecto, incluso si se declaran con const. Para crear objetos inmutables, necesitas usar técnicas específicas que prevengan la mutación de propiedades. Las técnicas principales son Object.freeze(), el spread operator, y Object.assign().

Object.freeze()

Object.freeze() es un método que congela un objeto, previniendo adiciones, eliminaciones y modificaciones de sus propiedades. Sin embargo, solo congela el objeto en el nivel superior; los objetos anidados no se congelan automáticamente y pueden mutarse.

object-freeze.js
Loading code...

El ejemplo muestra cómo usar Object.freeze() para crear un objeto inmutable. El objeto usuario está congelado, por lo que intentar agregar, eliminar o modificar sus propiedades lanza un error en modo estricto. Sin embargo, el objeto anidado direccion no está congelado y puede modificarse.

Limitación de Object.freeze()

Object.freeze() solo congela el objeto en el nivel superior. Los objetos anidados no se congelan automáticamente. Para congelar objetos anidados, necesitas aplicar Object.freeze() recursivamente o usar una función que congele todos los niveles del objeto.

Spread Operator (...)

El spread operator (...) es una técnica moderna para crear copias de objetos y arrays. Al usar el spread operator, creas una nueva referencia en lugar de modificar la existente, lo que hace que el original permanezca inmutable. Esta es la técnica preferida en JavaScript moderno.

spread-operator.js
Loading code...

El ejemplo muestra cómo usar el spread operator para crear copias inmutables. Cuando creas usuarioActualizado usando el spread operator, el usuario original permanezca inmutable. Cualquier cambio en usuarioActualizado no afecta al usuario original.

Spread operator es la técnica preferida

El spread operator es la técnica más moderna y legible para crear copias inmutables en JavaScript. Funciona tanto para objetos como para arrays, y es compatible con navegadores modernos. Es más conciso que Object.assign() y más claro que Object.freeze() para la mayoría de casos.

Object.assign()

Object.assign() es un método que copia propiedades de uno o más objetos a un objeto destino. Para crear un objeto inmutable, pasas un objeto vacío como primer argumento, lo que crea una nueva referencia en lugar de modificar el objeto original.

object-assign.js
Loading code...

El ejemplo muestra cómo usar Object.assign() para crear copias inmutables. Al pasar un objeto vacío como primer argumento, Object.assign() crea una nueva referencia en lugar de modificar el usuario original. El usuario original permanezca inmutable.

Arrays Inmutables

Los arrays en JavaScript son mutables por defecto, incluso si se declaran con const. Para trabajar con arrays inmutables, puedes usar métodos que crean nuevos arrays en lugar de mutar el existente, o usar el spread operator para crear copias.

Métodos que Crean Arrays Inmutables

JavaScript proporciona varios métodos que crean nuevos arrays en lugar de mutar el existente. Métodos como map(), filter(), reduce(), slice() y concat() devuelven nuevos arrays, dejando el original inmutable. Estos métodos son ideales para programación funcional y evitan mutaciones inesperadas.

metodos-array-inmutables.js
Loading code...

El ejemplo muestra cómo usar métodos inmutables de array. map() crea un nuevo array con cada elemento duplicado, filter() crea un nuevo array con solo los elementos que cumplen la condición, y slice() crea una nueva porción del array. En todos los casos, el array original permanezca inmutable.

Métodos mutables vs inmutables

Métodos mutables: push(), pop(), shift(), unshift(), splice(), sort()
Métodos inmutables: map(), filter(), reduce(), slice(), concat()
Los métodos inmutables crean nuevos arrays, mientras que los mutables modifican el existente.

Spread Operator para Arrays

El spread operator también funciona con arrays para crear copias inmutables. Puedes usarlo para crear un nuevo array con todos los elementos del original, o para agregar elementos sin mutar el array existente. Esta técnica es especialmente útil para agregar elementos al inicio de un array.

spread-arrays.js
Loading code...

El ejemplo muestra cómo usar el spread operator con arrays. Al crear nuevoArray con el spread operator, creas una nueva referencia en lugar de mutar arrayOriginal. Puedes agregar elementos al inicio o al final sin modificar el array original.

Métodos que No Mutan

JavaScript proporciona varios métodos que crean nuevos objetos o arrays en lugar de mutar los existentes. Estos métodos son fundamentales para la programación funcional y te ayudan a evitar mutaciones inesperadas que pueden causar bugs difíciles de rastrear.

Object.keys() y Object.values()

Object.keys() devuelve un array con las claves de un objeto, mientras que Object.values() devuelve un array con los valores. Estos métodos son útiles para transformar objetos en arrays inmutables, que luego puedes procesar con métodos inmutables de array.

object-keys-values.js
Loading code...

El ejemplo muestra cómo usar Object.keys() y Object.values() para transformar un objeto en arrays inmutables. Al extraer las claves y valores, creas nuevos arrays que puedes procesar con métodos inmutables como map() y filter(). El objeto original permanezca inmutable.

Object.entries()

Object.entries() devuelve un array de arrays, donde cada sub-array contiene [clave, valor]. Este método es especialmente útil para transformar objetos en nuevos objetos inmutables, ya que puedes procesar cada par clave-valor y crear un nuevo objeto.

object-entries.js
Loading code...

El ejemplo muestra cómo usar Object.entries() para crear un nuevo objeto inmutable. Al procesar cada par [clave, valor] y crear un nuevo objeto con reduce(), creas una nueva referencia en lugar de modificar el objeto original. La combinación de Object.entries(), reduce() y el spread operator es un patrón poderoso para transformar objetos inmutables, ideal para estados en aplicaciones React y Redux.

Patrones de Código Inmutable

Hay patrones comunes que usan inmutabilidad para crear código más robusto y mantenible. Estos patrones incluyen actualización de estado inmutable, reducers en Redux, y transformaciones funcionales. Entender estos patrones te ayudará a escribir código más limpio y predecible.

Actualización de Estado Inmutable

En aplicaciones modernas, especialmente con React y Redux, es común mantener el estado inmutable y crear nuevas versiones para cada actualización. En lugar de mutar el estado existente, creas una nueva versión que incorpora los cambios. Esto facilita el rastreo de cambios y el debugging.

estado-inmutable.js
Loading code...

El ejemplo muestra cómo actualizar el estado inmutable. La función actualizarUsuario() crea una nueva versión del estado en lugar de mutar el existente. El estado original permanezca inmutable, lo que facilita el rastreo de cambios y el debugging.

Beneficios del estado inmutable

Time travel: Puedes volver a versiones anteriores del estado
Comparación fácil: Puedes comparar estados con ===
Debugging simplificado: Los cambios son explícitos y rastreables
Predecibilidad: El comportamiento es más predecible

Reducers en Redux

Los reducers en Redux son funciones puras que toman el estado anterior y una acción, y devuelven el nuevo estado. Los reducers deben ser inmutables: nunca deben mutar el estado, siempre deben devolver una nueva versión. Este patrón es fundamental para el correcto funcionamiento de Redux.

reducers-redux.js
Loading code...

El ejemplo muestra un reducer inmutable en Redux. El reducer toma el estado anterior y una acción, y devuelve el nuevo estado usando el spread operator. El estado anterior permanezca inmutable, lo que permite características como time travel y undo/redo. Redux Toolkit simplifica la creación de reducers inmutables usando Immer, una librería que permite escribir código que parece mutable pero genera código inmutable.

Cuándo NO Usar Inmutabilidad

Aunque la inmutabilidad tiene muchos beneficios, no siempre es la mejor opción. Hay situaciones donde la mutabilidad es más apropiada o eficiente, y aplicar inmutabilidad indiscriminadamente puede causar problemas de rendimiento o hacer el código más complejo de lo necesario.

  • <strong>Operaciones intensivas con datos grandes:</strong> procesamiento de archivos, manipulación de imágenes, cálculos matemáticos complejos
  • <strong>Loops con muchas iteraciones:</strong> cuando el costo de crear copias supera los beneficios de inmutabilidad
  • <strong>Código de UI o manipulación del DOM:</strong> donde la mutación directa es más natural y eficiente
  • <strong>Aplicaciones con restricciones de memoria:</strong> dispositivos móviles o entornos con recursos limitados

Consideraciones de Rendimiento

La inmutabilidad tiene un costo de rendimiento porque crear copias de objetos y arrays consume memoria y tiempo de procesamiento. Para aplicaciones pequeñas o con datos que cambian frecuentemente, la mutabilidad puede ser más eficiente. La clave es evaluar el trade-off entre mantenibilidad y rendimiento.

rendimiento-inmutabilidad.js
Loading code...

El ejemplo muestra el costo de rendimiento de la inmutabilidad. Crear 100,000 copias de un objeto consume más tiempo y memoria que mutar el objeto 100,000 veces. Para datos grandes o operaciones frecuentes, la mutación puede ser más eficiente. Los motores modernos de JavaScript optimizan código inmutable mediante técnicas como structural sharing y escape analysis, pero la mutación excesiva puede impedir estas optimizaciones.

Complejidad vs Simplicidad

La inmutabilidad puede hacer el código más complejo y difícil de leer para desarrolladores no familiarizados con programación funcional. Crear copias de objetos y usar métodos inmutables puede resultar en código que parece más complejo que una mutación simple. La clave es encontrar un balance entre inmutabilidad y legibilidad.

complejidad-inmutabilidad.js
Loading code...

El ejemplo muestra cómo la inmutabilidad puede aumentar la complejidad del código. La versión inmutable usa el spread operator y map(), lo que puede ser menos legible para desarrolladores no familiarizados con programación funcional. La versión mutable es más simple pero más propensa a bugs. No sacrifiques legibilidad por inmutabilidad: para código de UI o manipulación del DOM, la mutación puede ser más apropiada; para lógica de negocio y gestión de estado, la inmutabilidad suele ser mejor.

Resumen: Inmutabilidad

Conceptos principales:

  • const crea variables inmutables que no pueden reasignarse
  • Object.freeze() congela objetos para prevenir mutaciones
  • Spread operator crea copias inmutables de objetos y arrays
  • Métodos como map() y filter() crean arrays inmutables
  • La inmutabilidad reduce bugs causados por mutaciones inesperadas

Mejores prácticas:

  • Usa const para valores que no deben cambiar
  • Prefiere spread operator sobre Object.assign() para copias
  • Usa métodos inmutables de array (map, filter, reduce)
  • Actualiza el estado creando nuevas versiones, no mutando
  • Evalúa el trade-off entre inmutabilidad y rendimiento