Back

Arrays No Mutantes: Escribiendo Código JavaScript Más Seguro

Arrays No Mutantes: Escribiendo Código JavaScript Más Seguro

Cuando modificas un array en JavaScript, podrías cambiar accidentalmente datos de los que dependen otras partes de tu código. Esto crea bugs que son difíciles de rastrear. ¿La solución? Usar métodos de array no mutantes que devuelven nuevos arrays en lugar de cambiar el original.

Este artículo cubre los métodos de array no mutantes esenciales en JavaScript, por qué son importantes para escribir código más seguro, y cómo usarlos efectivamente en tus proyectos.

Puntos Clave

  • Los métodos de array no mutantes devuelven nuevos arrays sin cambiar los datos originales
  • Usar operaciones inmutables previene efectos secundarios inesperados y hace el código más predecible
  • Métodos como map(), filter(), y reduce() son alternativas más seguras a las operaciones mutantes
  • El operador spread proporciona una sintaxis limpia para operaciones comunes de arrays

Por Qué Importa la Inmutabilidad en JavaScript

Mutar arrays puede causar comportamientos inesperados en tus aplicaciones. Cuando pasas un array a una función o lo compartes entre componentes, las modificaciones en un lugar afectan todas las referencias a ese array.

const originalTasks = ['Write code', 'Review PR', 'Deploy'];
const completedTasks = originalTasks;
completedTasks.push('Write tests');

console.log(originalTasks); // ['Write code', 'Review PR', 'Deploy', 'Write tests']
// ¡El array original fue cambiado!

Esto se vuelve especialmente problemático en aplicaciones React donde las mutaciones de estado impiden que los componentes se vuelvan a renderizar, o en Redux donde el estado debe permanecer inmutable.

Métodos Mutantes vs No Mutantes: Diferencias Clave

Métodos Mutantes (Evita Estos)

  • push(), pop(), shift(), unshift() - Agregan o remueven elementos
  • sort() - Ordena el array en su lugar
  • reverse() - Invierte el orden del array
  • splice() - Agrega/remueve elementos en cualquier posición
  • fill() - Llena el array con un valor

Métodos No Mutantes (Usa Estos)

  • map() - Transforma cada elemento
  • filter() - Mantiene elementos que coinciden con una condición
  • reduce() - Combina elementos en un solo valor
  • slice() - Extrae una porción del array
  • concat() - Combina arrays

Métodos de Array No Mutantes Esenciales

map(): Transformar Sin Mutación

En lugar de usar un bucle for que modifica un array, map() crea un nuevo array con valores transformados:

const prices = [10, 20, 30];
const discountedPrices = prices.map(price => price * 0.8);

console.log(prices);          // [10, 20, 30] - sin cambios
console.log(discountedPrices); // [8, 16, 24]

filter(): Filtrado Seguro de Arrays

Remueve elementos sin tocar el array original:

const users = [
  { name: 'Alice', active: true },
  { name: 'Bob', active: false },
  { name: 'Charlie', active: true }
];

const activeUsers = users.filter(user => user.active);
console.log(users.length);       // 3 - original sin cambios
console.log(activeUsers.length); // 2

reduce(): Combinar Sin Efectos Secundarios

Calcula valores de arrays sin variables externas:

const orders = [
  { product: 'Laptop', price: 1200 },
  { product: 'Mouse', price: 25 }
];

const total = orders.reduce((sum, order) => sum + order.price, 0);
// Devuelve 1225 sin modificar orders

slice(): Extraer Porciones de Array

Obtén un subconjunto de un array sin usar splice():

const tasks = ['Task 1', 'Task 2', 'Task 3', 'Task 4'];
const firstTwo = tasks.slice(0, 2);
const lastTwo = tasks.slice(-2);

console.log(firstTwo); // ['Task 1', 'Task 2']
console.log(lastTwo);  // ['Task 3', 'Task 4']
console.log(tasks);    // Original sin cambios

concat(): Combinar Arrays de Forma Segura

Fusiona arrays sin usar push():

const completed = ['Task 1', 'Task 2'];
const pending = ['Task 3', 'Task 4'];
const allTasks = completed.concat(pending);

// O usa el operador spread
const allTasksSpread = [...completed, ...pending];

Mejores Prácticas para Arrays No Mutantes en JavaScript

1. Reemplaza Operaciones Mutantes

// ❌ Evita: Mutar con push
const items = [1, 2, 3];
items.push(4);

// ✅ Mejor: Crear nuevo array
const newItems = [...items, 4];

2. Encadena Métodos para Operaciones Complejas

const products = [
  { name: 'Laptop', price: 1200, inStock: true },
  { name: 'Phone', price: 800, inStock: false },
  { name: 'Tablet', price: 600, inStock: true }
];

const affordableInStock = products
  .filter(p => p.inStock)
  .filter(p => p.price < 1000)
  .map(p => p.name);
// Devuelve ['Tablet'] sin modificar products

3. Usa el Operador Spread para Operaciones Simples

// Remover elemento en índice
const removeAt = (arr, index) => [
  ...arr.slice(0, index),
  ...arr.slice(index + 1)
];

// Actualizar elemento en índice
const updateAt = (arr, index, value) => [
  ...arr.slice(0, index),
  value,
  ...arr.slice(index + 1)
];

Gestión de Estado Más Segura en React

Los métodos no mutantes son esenciales para las actualizaciones de estado en React:

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', done: false }
  ]);

  const toggleTodo = (id) => {
    // ✅ Crea nuevo array con objeto actualizado
    setTodos(todos.map(todo =>
      todo.id === id 
        ? { ...todo, done: !todo.done }
        : todo
    ));
  };

  const removeTodo = (id) => {
    // ✅ Filtra el todo sin mutación
    setTodos(todos.filter(todo => todo.id !== id));
  };
}

Consideraciones de Rendimiento

Aunque los métodos no mutantes crean nuevos arrays, los motores JavaScript modernos optimizan bien estas operaciones. Los beneficios de código predecible y libre de bugs usualmente superan las diferencias menores de rendimiento. Para código crítico en rendimiento con grandes conjuntos de datos, considera usar librerías especializadas como Immutable.js o Immer.

Conclusión

Los métodos de array no mutantes hacen tu código JavaScript más predecible y fácil de depurar. Al usar map(), filter(), reduce(), slice(), y concat() en lugar de sus contrapartes mutantes, evitas efectos secundarios que llevan a bugs. Este enfoque es especialmente valioso en aplicaciones React y cuando sigues principios de programación funcional. Comienza a reemplazar operaciones mutantes en tu código hoy—tu yo futuro te lo agradecerá.

Preguntas Frecuentes

Sí, pero sé consistente dentro de cada contexto. Usa métodos no mutantes para datos compartidos, gestión de estado, y programación funcional. Los métodos mutantes pueden ser aceptables para arrays temporales locales que no serán referenciados en otro lugar.

La diferencia de rendimiento es insignificante para la mayoría de aplicaciones. Los motores JavaScript modernos optimizan estas operaciones eficientemente. Solo considera alternativas para conjuntos de datos extremadamente grandes o bucles críticos en rendimiento después de que el profiling confirme un cuello de botella.

Usa el operador spread o slice para crear una copia primero, luego ordena la copia. Por ejemplo, const sorted = [...array].sort() o const sorted = array.slice().sort(). Esto preserva el orden del array original.

Slice es no mutante y devuelve un nuevo array conteniendo elementos extraídos sin cambiar el original. Splice es mutante y modifica directamente el array original removiendo o reemplazando elementos y devuelve los elementos removidos.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay