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()
, yreduce()
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 elementossort()
- Ordena el array en su lugarreverse()
- Invierte el orden del arraysplice()
- Agrega/remueve elementos en cualquier posiciónfill()
- Llena el array con un valor
Métodos No Mutantes (Usa Estos)
map()
- Transforma cada elementofilter()
- Mantiene elementos que coinciden con una condiciónreduce()
- Combina elementos en un solo valorslice()
- Extrae una porción del arrayconcat()
- 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];
Discover how at OpenReplay.com.
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.