JavaScript

JavaScript

Tutorial JavaScript: Arrays y Métodos

JavaScript array: creación y manejo. Domina la creación y manejo de arrays en JavaScript con ejemplos prácticos y detallados.

Aprende JavaScript y certifícate

Creación y estructura de arrays en JavaScript

Los arrays son una de las estructuras de datos fundamentales en JavaScript. Permiten almacenar múltiples valores en una única variable, organizados en forma de lista ordenada. A diferencia de otros lenguajes, los arrays en JavaScript son dinámicos y pueden contener elementos de diferentes tipos.

Creación de arrays

Existen varias formas de crear arrays en JavaScript:

1. Mediante la sintaxis literal de array

La forma más común y recomendada para crear arrays es utilizando corchetes []:

// Array vacío
const emptyArray = [];

// Array con elementos
const fruits = ["apple", "banana", "orange"];

// Array con diferentes tipos de datos
const mixedArray = [42, "hello", true, { name: "John" }, [1, 2, 3]];

2. Mediante el constructor Array()

Aunque menos utilizada, esta sintaxis también permite crear arrays:

// Array vacío
const emptyArray = new Array();

// Array con elementos predefinidos
const colors = new Array("red", "green", "blue");

// Array con longitud predefinida (todos los elementos son undefined)
const numbersArray = new Array(5);

Es importante destacar que el constructor Array() tiene un comportamiento especial: si se pasa un único argumento numérico, crea un array con esa longitud (con elementos vacíos), mientras que si se pasan múltiples argumentos o un único argumento no numérico, estos se convierten en elementos del array.

Estructura y características de los arrays

Los arrays en JavaScript tienen varias características importantes:

1. Índices basados en cero

Los elementos de un array se acceden mediante índices numéricos que comienzan en 0:

const planets = ["Mercury", "Venus", "Earth", "Mars"];
console.log(planets[0]); // "Mercury"
console.log(planets[2]); // "Earth"

2. Propiedad length

Todos los arrays tienen una propiedad length que indica el número de elementos:

const numbers = [10, 20, 30, 40, 50];
console.log(numbers.length); // 5

La propiedad length es dinámica y se actualiza automáticamente cuando se modifican los elementos del array. También podemos modificarla directamente:

const letters = ["a", "b", "c"];
letters.length = 5; // Extiende el array con elementos vacíos
console.log(letters); // ["a", "b", "c", empty × 2]

letters.length = 2; // Trunca el array
console.log(letters); // ["a", "b"]

3. Arrays dispersos (sparse arrays)

JavaScript permite crear arrays con "huecos" o posiciones vacías:

const sparseArray = [1, , 3, 4];
console.log(sparseArray); // [1, empty, 3, 4]
console.log(sparseArray.length); // 4

// También se pueden crear asignando a un índice específico
const anotherSparse = [];
anotherSparse[0] = "first";
anotherSparse[3] = "fourth";
console.log(anotherSparse); // ["first", empty × 2, "fourth"]

Estos huecos son diferentes de valores como undefined o null, y algunos métodos de array los tratan de manera especial.

Modificación de arrays

Los arrays en JavaScript son mutables, lo que significa que podemos modificar sus elementos después de crearlos:

1. Modificación de elementos existentes

const scores = [75, 80, 95, 62];
scores[2] = 98; // Modifica el tercer elemento
console.log(scores); // [75, 80, 98, 62]

2. Añadir nuevos elementos

const team = ["Alice", "Bob"];
team[2] = "Charlie"; // Añade un elemento al final
console.log(team); // ["Alice", "Bob", "Charlie"]

team[5] = "Dave"; // Crea un array disperso
console.log(team); // ["Alice", "Bob", "Charlie", empty × 2, "Dave"]

3. Eliminar elementos

El operador delete elimina el valor de una posición pero mantiene el "hueco":

const items = ["pen", "book", "laptop", "phone"];
delete items[1];
console.log(items); // ["pen", empty, "laptop", "phone"]
console.log(items.length); // 4 (la longitud no cambia)

Arrays multidimensionales

JavaScript permite crear arrays de arrays, lo que nos da estructuras multidimensionales:

// Array bidimensional (matriz)
const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

console.log(matrix[1][2]); // 6 (segundo array, tercer elemento)

// Array tridimensional
const cube = [
  [[1, 2], [3, 4]],
  [[5, 6], [7, 8]]
];

console.log(cube[1][0][1]); // 6

Desestructuración de arrays

La desestructuración es una característica moderna de JavaScript que permite extraer valores de arrays de forma concisa:

const rgb = [255, 100, 50];

// Desestructuración básica
const [red, green, blue] = rgb;
console.log(red);   // 255
console.log(green); // 100
console.log(blue);  // 50

// Omitir elementos
const [first, , third] = [10, 20, 30, 40];
console.log(first); // 10
console.log(third); // 30

// Valores por defecto
const [name = "Guest", age = 25] = ["Alice"];
console.log(name); // "Alice"
console.log(age);  // 25 (valor por defecto)

// Resto de elementos
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

Comprobación de arrays

Para verificar si una variable es un array, usamos el método Array.isArray():

console.log(Array.isArray([]));           // true
console.log(Array.isArray([1, 2, 3]));    // true
console.log(Array.isArray(new Array()));  // true
console.log(Array.isArray("string"));     // false
console.log(Array.isArray({}));           // false
console.log(Array.isArray(null));         // false

Este método es más fiable que usar instanceof porque funciona correctamente incluso entre diferentes contextos (como iframes).

Arrays tipados

JavaScript también ofrece arrays tipados para trabajar con datos binarios. Estos son más eficientes para operaciones numéricas y tienen un tamaño fijo:

// Array de enteros de 8 bits sin signo
const uint8Array = new Uint8Array([1, 2, 3, 4]);
console.log(uint8Array[0]); // 1

// Array de números de punto flotante de 32 bits
const float32Array = new Float32Array(3);
float32Array[0] = 1.5;
float32Array[1] = 2.5;
float32Array[2] = 3.5;
console.log(float32Array); // Float32Array(3) [1.5, 2.5, 3.5]

Los arrays tipados son especialmente útiles para aplicaciones que requieren alto rendimiento como procesamiento de gráficos, audio o manipulación de archivos binarios.

Conversión entre arrays y otros tipos

1. Convertir strings a arrays

// Usando split()
const sentence = "Hello world";
const words = sentence.split(" ");
console.log(words); // ["Hello", "world"]

// Usando Array.from()
const chars = Array.from("hello");
console.log(chars); // ["h", "e", "l", "l", "o"]

// Usando el operador spread
const letters = [..."JavaScript"];
console.log(letters); // ["J", "a", "v", "a", "S", "c", "r", "i", "p", "t"]

2. Convertir arrays a strings

const fruits = ["apple", "banana", "cherry"];
const fruitString = fruits.join(", ");
console.log(fruitString); // "apple, banana, cherry"

// Conversión implícita
console.log(fruits.toString()); // "apple,banana,cherry"

3. Convertir objetos iterables a arrays

// Desde un Set
const uniqueNumbers = new Set([1, 2, 2, 3, 4, 4]);
const numbersArray = Array.from(uniqueNumbers);
console.log(numbersArray); // [1, 2, 3, 4]

// Desde un Map
const map = new Map([["name", "John"], ["age", 30]]);
const entries = Array.from(map);
console.log(entries); // [["name", "John"], ["age", 30]]

// Usando el operador spread
const setToArray = [...uniqueNumbers];
console.log(setToArray); // [1, 2, 3, 4]

Copiado de arrays

Es importante entender que cuando asignamos un array a una nueva variable, solo se copia la referencia, no los datos:

const original = [1, 2, 3];
const reference = original; // Copia la referencia, no los datos

reference[0] = 99;
console.log(original); // [99, 2, 3] - ¡El original también cambió!

Para crear copias reales de arrays, podemos usar varios métodos:

const original = [1, 2, 3];

// Usando el operador spread (shallow copy)
const copy1 = [...original];

// Usando slice() (shallow copy)
const copy2 = original.slice();

// Usando Array.from() (shallow copy)
const copy3 = Array.from(original);

// Usando concat() (shallow copy)
const copy4 = [].concat(original);

// Copia profunda para arrays anidados
const nested = [1, [2, 3], {a: 4}];
const deepCopy = JSON.parse(JSON.stringify(nested));

Todas estas técnicas crean copias superficiales (shallow copies), lo que significa que los objetos anidados dentro del array seguirán siendo referencias. Para copias profundas de estructuras complejas, se puede usar JSON.parse(JSON.stringify()) o bibliotecas como Lodash.

Los arrays en JavaScript son extremadamente versátiles y forman la base de muchas operaciones de programación. Dominar su creación y estructura es fundamental antes de adentrarse en los métodos más avanzados que veremos en las siguientes secciones.

Métodos de transformación y manipulación de arrays

JavaScript ofrece un conjunto robusto de métodos integrados para transformar y manipular arrays, permitiéndonos modificar su estructura, orden y contenido. Estos métodos son fundamentales para el desarrollo moderno y nos ayudan a escribir código más limpio y expresivo.

Métodos para añadir y eliminar elementos

Añadir elementos

JavaScript proporciona varios métodos para añadir elementos a un array:

1. push() - Añade elementos al final

El método push() añade uno o más elementos al final del array y devuelve la nueva longitud:

const technologies = ["HTML", "CSS"];
const newLength = technologies.push("JavaScript", "React");

console.log(technologies); // ["HTML", "CSS", "JavaScript", "React"]
console.log(newLength);    // 4

2. unshift() - Añade elementos al principio

El método unshift() inserta elementos al inicio del array y devuelve la nueva longitud:

const numbers = [3, 4, 5];
const newLength = numbers.unshift(1, 2);

console.log(numbers);   // [1, 2, 3, 4, 5]
console.log(newLength); // 5

Eliminar elementos

1. pop() - Elimina el último elemento

El método pop() elimina el último elemento del array y lo devuelve:

const stack = ["first", "second", "third"];
const removed = stack.pop();

console.log(stack);   // ["first", "second"]
console.log(removed); // "third"

2. shift() - Elimina el primer elemento

El método shift() elimina el primer elemento del array y lo devuelve:

const queue = ["first", "second", "third"];
const removed = queue.shift();

console.log(queue);   // ["second", "third"]
console.log(removed); // "first"

Manipulación en posiciones específicas

1. splice() - Elimina, reemplaza o añade elementos

El método splice() es extremadamente versátil y permite modificar un array eliminando, reemplazando o añadiendo elementos en cualquier posición:

const months = ["Jan", "March", "April", "June"];

// Insertar en el índice 1
months.splice(1, 0, "Feb");
console.log(months); // ["Jan", "Feb", "March", "April", "June"]

// Reemplazar en el índice 4
months.splice(4, 1, "May");
console.log(months); // ["Jan", "Feb", "March", "April", "May"]

// Eliminar 2 elementos a partir del índice 2
const removed = months.splice(2, 2);
console.log(months);  // ["Jan", "Feb", "May"]
console.log(removed); // ["March", "April"]

La sintaxis de splice() es: array.splice(startIndex, deleteCount, item1, item2, ...).

Métodos para combinar y dividir arrays

1. concat() - Combina arrays

El método concat() crea un nuevo array combinando dos o más arrays:

const firstHalf = [1, 2, 3];
const secondHalf = [4, 5, 6];
const combined = firstHalf.concat(secondHalf);

console.log(combined); // [1, 2, 3, 4, 5, 6]
console.log(firstHalf); // [1, 2, 3] - El array original no se modifica

// Podemos concatenar múltiples arrays
const extras = [7, 8];
const all = firstHalf.concat(secondHalf, extras, [9, 10]);
console.log(all); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

2. slice() - Extrae una porción del array

El método slice() devuelve una copia superficial de una porción del array sin modificar el original:

const animals = ["ant", "bison", "camel", "duck", "elephant"];

// Desde el índice 2 hasta el final
console.log(animals.slice(2)); // ["camel", "duck", "elephant"]

// Desde el índice 2 hasta el 4 (sin incluir el 4)
console.log(animals.slice(2, 4)); // ["camel", "duck"]

// Desde el penúltimo hasta el final
console.log(animals.slice(-2)); // ["duck", "elephant"]

// Copia completa del array
const animalsCopy = animals.slice();
console.log(animalsCopy); // ["ant", "bison", "camel", "duck", "elephant"]

3. Operador spread (...) - Combina arrays

El operador spread es una forma moderna y concisa de combinar arrays:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];

console.log(combined); // [1, 2, 3, 4, 5, 6]

// Podemos insertar elementos adicionales
const withExtras = [...arr1, 'a', 'b', ...arr2];
console.log(withExtras); // [1, 2, 3, "a", "b", 4, 5, 6]

Métodos para transformar arrays

1. map() - Transforma cada elemento

El método map() crea un nuevo array con los resultados de aplicar una función a cada elemento:

const numbers = [1, 4, 9, 16];
const squareRoots = numbers.map(num => Math.sqrt(num));

console.log(squareRoots); // [1, 2, 3, 4]

// Transformación de objetos
const products = [
  { id: 1, name: "Laptop", price: 1000 },
  { id: 2, name: "Phone", price: 800 },
  { id: 3, name: "Tablet", price: 500 }
];

const productNames = products.map(product => product.name);
console.log(productNames); // ["Laptop", "Phone", "Tablet"]

// Transformación con índice
const indexed = numbers.map((num, index) => `Item ${index}: ${num}`);
console.log(indexed); // ["Item 0: 1", "Item 1: 4", "Item 2: 9", "Item 3: 16"]

2. flat() - Aplana arrays anidados

El método flat() crea un nuevo array con todos los elementos de sub-arrays concatenados recursivamente hasta la profundidad especificada:

const nestedArray = [1, 2, [3, 4, [5, 6]]];

// Aplanar un nivel
console.log(nestedArray.flat()); // [1, 2, 3, 4, [5, 6]]

// Aplanar dos niveles
console.log(nestedArray.flat(2)); // [1, 2, 3, 4, 5, 6]

// Aplanar todos los niveles
console.log(nestedArray.flat(Infinity)); // [1, 2, 3, 4, 5, 6]

// Útil para eliminar espacios vacíos
const sparseArray = [1, 2, , 4, 5];
console.log(sparseArray.flat()); // [1, 2, 4, 5]

3. flatMap() - Combinación de map() y flat()

El método flatMap() aplica una función a cada elemento y luego aplana el resultado en un nivel:

const sentences = ["Hello world", "JavaScript is fun"];
const words = sentences.flatMap(sentence => sentence.split(" "));

console.log(words); // ["Hello", "world", "JavaScript", "is", "fun"]

// Equivalente a:
// sentences.map(sentence => sentence.split(" ")).flat();

// Ejemplo más complejo
const cartItems = [
  { product: "Laptop", quantity: 1 },
  { product: "Mouse", quantity: 2 }
];

const expandedItems = cartItems.flatMap(item => {
  return Array(item.quantity).fill(item.product);
});

console.log(expandedItems); // ["Laptop", "Mouse", "Mouse"]

Métodos para reordenar arrays

1. reverse() - Invierte el orden

El método reverse() invierte el orden de los elementos de un array in situ:

const letters = ["a", "b", "c", "d"];
letters.reverse();

console.log(letters); // ["d", "c", "b", "a"]

// Para no modificar el original
const original = [1, 2, 3, 4];
const reversed = [...original].reverse();

console.log(original);  // [1, 2, 3, 4]
console.log(reversed);  // [4, 3, 2, 1]

2. sort() - Ordena los elementos

El método sort() ordena los elementos de un array in situ y devuelve el array ordenado:

// Ordenación alfabética por defecto
const fruits = ["banana", "apple", "orange", "mango"];
fruits.sort();
console.log(fruits); // ["apple", "banana", "mango", "orange"]

// Ordenación numérica (requiere función de comparación)
const numbers = [40, 1, 5, 200];
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 5, 40, 200]

// Ordenación de objetos
const people = [
  { name: "John", age: 30 },
  { name: "Alice", age: 25 },
  { name: "Bob", age: 35 }
];

// Ordenar por edad
people.sort((a, b) => a.age - b.age);
console.log(people); 
// [{ name: "Alice", age: 25 }, { name: "John", age: 30 }, { name: "Bob", age: 35 }]

// Ordenar por nombre
people.sort((a, b) => a.name.localeCompare(b.name));
console.log(people);
// [{ name: "Alice", age: 25 }, { name: "Bob", age: 35 }, { name: "John", age: 30 }]

Métodos para transformar arrays en otros valores

1. join() - Convierte un array en string

El método join() une todos los elementos de un array en un string:

const elements = ["Fire", "Air", "Water"];

console.log(elements.join());      // "Fire,Air,Water"
console.log(elements.join(""));    // "FireAirWater"
console.log(elements.join(" "));   // "Fire Air Water"
console.log(elements.join(" - ")); // "Fire - Air - Water"

2. reduce() - Reduce a un único valor

El método reduce() ejecuta una función reductora sobre cada elemento, resultando en un único valor:

// Suma de números
const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((accumulator, current) => accumulator + current, 0);
console.log(sum); // 10

// Concatenación de strings
const words = ["Hello", "beautiful", "world"];
const sentence = words.reduce((acc, word) => `${acc} ${word}`, "").trim();
console.log(sentence); // "Hello beautiful world"

// Conteo de ocurrencias
const fruits = ["apple", "banana", "apple", "orange", "banana", "apple"];
const count = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});

console.log(count); // { apple: 3, banana: 2, orange: 1 }

// Aplanar array de arrays
const nestedArrays = [[1, 2], [3, 4], [5, 6]];
const flattened = nestedArrays.reduce((acc, arr) => [...acc, ...arr], []);
console.log(flattened); // [1, 2, 3, 4, 5, 6]

Métodos para crear nuevos arrays

1. Array.from() - Crea arrays desde iterables

El método Array.from() crea un nuevo array a partir de un objeto iterable o array-like:

// Desde string
console.log(Array.from("hello")); // ["h", "e", "l", "l", "o"]

// Desde Set
const uniqueValues = new Set([1, 2, 2, 3, 1]);
console.log(Array.from(uniqueValues)); // [1, 2, 3]

// Con función de mapeo
console.log(Array.from([1, 2, 3], x => x * 2)); // [2, 4, 6]

// Crear array con valores específicos
console.log(Array.from({ length: 5 }, (_, i) => i + 1)); // [1, 2, 3, 4, 5]

2. Array.of() - Crea arrays desde argumentos

El método Array.of() crea un nuevo array con los argumentos proporcionados:

console.log(Array.of(1, 2, 3)); // [1, 2, 3]
console.log(Array.of("a", "b", "c")); // ["a", "b", "c"]

// Diferencia con el constructor Array
console.log(new Array(3));    // [empty × 3] (array con 3 slots vacíos)
console.log(Array.of(3));     // [3] (array con un elemento: 3)

Métodos para rellenar arrays

1. fill() - Rellena con un valor estático

El método fill() cambia todos los elementos de un array por un valor estático:

// Rellenar todo el array
const array1 = [1, 2, 3, 4];
console.log(array1.fill(0)); // [0, 0, 0, 0]

// Rellenar desde una posición
const array2 = [1, 2, 3, 4];
console.log(array2.fill(0, 2)); // [1, 2, 0, 0]

// Rellenar un rango
const array3 = [1, 2, 3, 4];
console.log(array3.fill(0, 1, 3)); // [1, 0, 0, 4]

// Crear y rellenar en un paso
const zeros = new Array(5).fill(0);
console.log(zeros); // [0, 0, 0, 0, 0]

Métodos para copiar dentro del array

1. copyWithin() - Copia una secuencia dentro del mismo array

El método copyWithin() copia una secuencia de elementos dentro del mismo array:

const array = [1, 2, 3, 4, 5];

// Copiar los elementos 3 y 4 a las posiciones 0 y 1
console.log(array.copyWithin(0, 2, 4)); // [3, 4, 3, 4, 5]

// Copiar los dos últimos elementos al principio
const array2 = [1, 2, 3, 4, 5];
console.log(array2.copyWithin(0, -2)); // [4, 5, 3, 4, 5]

Patrones comunes de manipulación de arrays

1. Eliminar duplicados

// Usando Set
const numbers = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3, 4, 5]

// Usando filter
const uniqueFilter = numbers.filter((value, index, self) => {
  return self.indexOf(value) === index;
});
console.log(uniqueFilter); // [1, 2, 3, 4, 5]

2. Agrupar elementos por una propiedad

const students = [
  { name: "Alice", grade: "A" },
  { name: "Bob", grade: "B" },
  { name: "Charlie", grade: "A" },
  { name: "David", grade: "C" }
];

const groupedByGrade = students.reduce((groups, student) => {
  const grade = student.grade;
  groups[grade] = groups[grade] || [];
  groups[grade].push(student);
  return groups;
}, {});

console.log(groupedByGrade);
/*
{
  A: [{ name: "Alice", grade: "A" }, { name: "Charlie", grade: "A" }],
  B: [{ name: "Bob", grade: "B" }],
  C: [{ name: "David", grade: "C" }]
}
*/

3. Transformación de datos encadenando métodos

const transactions = [
  { id: 1, type: "debit", amount: 100 },
  { id: 2, type: "credit", amount: 200 },
  { id: 3, type: "debit", amount: 50 },
  { id: 4, type: "credit", amount: 300 }
];

// Obtener la suma total de transacciones de crédito
const totalCredit = transactions
  .filter(t => t.type === "credit")
  .map(t => t.amount)
  .reduce((sum, amount) => sum + amount, 0);

console.log(totalCredit); // 500

4. Partición de arrays

// Dividir un array en chunks de tamaño específico
function chunkArray(array, size) {
  return Array.from(
    { length: Math.ceil(array.length / size) },
    (_, index) => array.slice(index * size, (index + 1) * size)
  );
}

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(chunkArray(numbers, 3));
// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

5. Intercalar arrays

// Intercalar elementos de dos arrays
function interleave(arr1, arr2) {
  return arr1.reduce((result, item, index) => {
    result.push(item);
    if (index < arr2.length) {
      result.push(arr2[index]);
    }
    return result;
  }, []);
}

const letters = ["a", "b", "c"];
const numbers = [1, 2, 3];
console.log(interleave(letters, numbers)); // ["a", 1, "b", 2, "c", 3]

Los métodos de transformación y manipulación de arrays en JavaScript proporcionan herramientas poderosas para trabajar con datos estructurados. Dominar estos métodos es esencial para escribir código JavaScript moderno, eficiente y expresivo. La combinación de estos métodos permite crear soluciones elegantes para problemas complejos de manipulación de datos.

Métodos de iteración y búsqueda en arrays

Los arrays en JavaScript ofrecen una amplia gama de métodos para iterar sobre sus elementos y realizar búsquedas eficientes. Estos métodos nos permiten recorrer, examinar y localizar elementos dentro de un array sin necesidad de escribir bucles tradicionales, lo que resulta en un código más limpio, expresivo y funcional.

Métodos de iteración básicos

forEach() - Iteración simple

El método forEach() ejecuta una función para cada elemento del array sin crear un nuevo array. Es la alternativa moderna y más legible a los bucles for tradicionales:

const colors = ["red", "green", "blue"];

colors.forEach((color, index) => {
  console.log(`Color at position ${index}: ${color}`);
});
// Output:
// Color at position 0: red
// Color at position 1: green
// Color at position 2: blue

Características importantes de forEach():

  • No devuelve ningún valor (siempre retorna undefined)
  • No se puede interrumpir (como con break en un bucle normal)
  • Ignora los elementos vacíos en arrays dispersos
  • Recibe tres argumentos: el valor actual, el índice y el array completo
// Ejemplo con todos los parámetros
const numbers = [1, 2, 3];
numbers.forEach((value, index, array) => {
  console.log(`${value} is at index ${index} in [${array}]`);
});

for...of - Iteración con sintaxis moderna

Aunque no es un método de array, el bucle for...of es una forma moderna de iterar sobre arrays:

const fruits = ["apple", "banana", "cherry"];

for (const fruit of fruits) {
  console.log(fruit);
}
// Output:
// apple
// banana
// cherry

Ventajas del bucle for...of:

  • Sintaxis más concisa que los bucles tradicionales
  • Permite usar break y continue para controlar el flujo
  • Funciona con cualquier objeto iterable (arrays, strings, maps, sets, etc.)
// Uso de break con for...of
const searchTerm = "banana";
for (const fruit of fruits) {
  if (fruit === searchTerm) {
    console.log(`Found ${searchTerm}!`);
    break; // Salir del bucle al encontrar el elemento
  }
}

Métodos de búsqueda de elementos

indexOf() y lastIndexOf() - Búsqueda por valor

Estos métodos buscan un elemento en el array y devuelven su índice:

const pets = ["dog", "cat", "bird", "cat", "fish"];

// indexOf() - encuentra la primera ocurrencia
console.log(pets.indexOf("cat")); // 1

// lastIndexOf() - encuentra la última ocurrencia
console.log(pets.lastIndexOf("cat")); // 3

// Si el elemento no existe, devuelve -1
console.log(pets.indexOf("turtle")); // -1

// Búsqueda a partir de un índice específico
console.log(pets.indexOf("cat", 2)); // 3

Estos métodos utilizan comparación estricta (===), lo que significa que distinguen entre tipos de datos:

const mixedArray = [1, "1", 2, "2"];
console.log(mixedArray.indexOf(1));  // 0
console.log(mixedArray.indexOf("1")); // 1

includes() - Verificación de existencia

El método includes() determina si un array contiene un determinado elemento y devuelve true o false:

const technologies = ["HTML", "CSS", "JavaScript", "React"];

console.log(technologies.includes("JavaScript")); // true
console.log(technologies.includes("Angular"));    // false

// Búsqueda a partir de un índice específico
console.log(technologies.includes("HTML", 1));    // false

Este método es especialmente útil para verificaciones de pertenencia:

const allowedRoles = ["admin", "editor", "moderator"];
const userRole = "editor";

if (allowedRoles.includes(userRole)) {
  console.log("Access granted");
} else {
  console.log("Access denied");
}

find() - Búsqueda con criterios personalizados

El método find() devuelve el primer elemento que cumple con una condición especificada:

const users = [
  { id: 1, name: "Alice", age: 25 },
  { id: 2, name: "Bob", age: 30 },
  { id: 3, name: "Charlie", age: 35 },
  { id: 4, name: "David", age: 30 }
];

// Encontrar el primer usuario con edad 30
const user = users.find(user => user.age === 30);
console.log(user); // { id: 2, name: "Bob", age: 30 }

// Si no encuentra nada, devuelve undefined
const admin = users.find(user => user.role === "admin");
console.log(admin); // undefined

El método find() es ideal para buscar objetos por sus propiedades:

// Función para buscar un usuario por ID
function findUserById(id) {
  return users.find(user => user.id === id);
}

console.log(findUserById(3)); // { id: 3, name: "Charlie", age: 35 }

findIndex() - Obtener el índice del elemento encontrado

Similar a find(), pero devuelve el índice del primer elemento que cumple la condición:

const products = [
  { id: 101, name: "Laptop", inStock: true },
  { id: 102, name: "Phone", inStock: false },
  { id: 103, name: "Tablet", inStock: true }
];

// Encontrar el índice del primer producto sin stock
const outOfStockIndex = products.findIndex(product => !product.inStock);
console.log(outOfStockIndex); // 1

// Si no encuentra nada, devuelve -1
const expensiveIndex = products.findIndex(product => product.price > 1000);
console.log(expensiveIndex); // -1

Este método es útil cuando necesitamos tanto encontrar un elemento como conocer su posición:

// Eliminar un producto por ID
function removeProduct(productId) {
  const index = products.findIndex(product => product.id === productId);
  if (index !== -1) {
    products.splice(index, 1);
    return true;
  }
  return false;
}

console.log(removeProduct(102)); // true
console.log(products.length);    // 2

findLast() y findLastIndex() - Búsqueda desde el final

Estos métodos más recientes buscan desde el final del array hacia el principio:

const numbers = [5, 12, 8, 130, 44, 10, 15];

// Encontrar el último número mayor que 10
const lastLarge = numbers.findLast(num => num > 10);
console.log(lastLarge); // 15

// Encontrar el índice del último número mayor que 10
const lastLargeIndex = numbers.findLastIndex(num => num > 10);
console.log(lastLargeIndex); // 6

Estos métodos son particularmente útiles cuando el orden de los elementos es importante y necesitamos encontrar la última ocurrencia.

Métodos de comprobación de condiciones

some() - Verificar si al menos un elemento cumple una condición

El método some() comprueba si al menos un elemento del array cumple con la condición especificada:

const ages = [16, 19, 21, 25];

// ¿Hay algún adulto? (edad >= 18)
const hasAdult = ages.some(age => age >= 18);
console.log(hasAdult); // true

// ¿Hay algún senior? (edad >= 65)
const hasSenior = ages.some(age => age >= 65);
console.log(hasSenior); // false

Este método es útil para validaciones rápidas:

const formData = [
  { field: "username", value: "john_doe" },
  { field: "email", value: "" },
  { field: "password", value: "secret123" }
];

// Verificar si hay campos vacíos
const hasEmptyFields = formData.some(field => field.value === "");
console.log(hasEmptyFields); // true

every() - Verificar si todos los elementos cumplen una condición

El método every() comprueba si todos los elementos del array cumplen con la condición especificada:

const numbers = [2, 4, 6, 8, 10];

// ¿Son todos pares?
const allEven = numbers.every(num => num % 2 === 0);
console.log(allEven); // true

// ¿Son todos mayores que 5?
const allGreaterThanFive = numbers.every(num => num > 5);
console.log(allGreaterThanFive); // false

Este método es excelente para validaciones completas:

const users = [
  { id: 1, active: true, lastLogin: "2023-01-15" },
  { id: 2, active: true, lastLogin: "2023-02-10" },
  { id: 3, active: true, lastLogin: "2023-01-30" }
];

// Verificar si todos los usuarios están activos
const allActive = users.every(user => user.active);
console.log(allActive); // true

Métodos de filtrado

filter() - Crear un nuevo array con elementos que cumplen una condición

El método filter() crea un nuevo array con todos los elementos que pasan la prueba implementada por la función proporcionada:

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Filtrar números pares
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4, 6, 8, 10]

// Filtrar números mayores que 5
const largeNumbers = numbers.filter(num => num > 5);
console.log(largeNumbers); // [6, 7, 8, 9, 10]

El método filter() es extremadamente útil para trabajar con colecciones de objetos:

const products = [
  { name: "Laptop", price: 1200, inStock: true },
  { name: "Phone", price: 800, inStock: true },
  { name: "Tablet", price: 500, inStock: false },
  { name: "Headphones", price: 200, inStock: true }
];

// Filtrar productos disponibles
const availableProducts = products.filter(product => product.inStock);
console.log(availableProducts.length); // 3

// Filtrar productos asequibles y disponibles
const affordableProducts = products.filter(
  product => product.price < 1000 && product.inStock
);
console.log(affordableProducts.length); // 2

También podemos usar filter() para eliminar elementos no deseados:

const values = [0, 1, false, 2, '', 3, null, undefined, 4, NaN];

// Filtrar valores falsy
const truthyValues = values.filter(Boolean);
console.log(truthyValues); // [1, 2, 3, 4]

// Filtrar valores nulos o indefinidos
const cleanArray = values.filter(value => value !== null && value !== undefined);
console.log(cleanArray); // [0, 1, false, 2, '', 3, 4, NaN]

Métodos de iteración avanzados

entries(), keys() y values() - Iteradores para arrays

Estos métodos devuelven objetos iteradores que podemos usar con bucles for...of:

const fruits = ["apple", "banana", "cherry"];

// entries() - devuelve pares [índice, valor]
for (const [index, fruit] of fruits.entries()) {
  console.log(`${index}: ${fruit}`);
}
// Output:
// 0: apple
// 1: banana
// 2: cherry

// keys() - devuelve los índices
for (const index of fruits.keys()) {
  console.log(index);
}
// Output:
// 0
// 1
// 2

// values() - devuelve los valores
for (const fruit of fruits.values()) {
  console.log(fruit);
}
// Output:
// apple
// banana
// cherry

Estos métodos son especialmente útiles cuando necesitamos acceso tanto a los índices como a los valores:

// Convertir a un objeto usando entries()
const fruitsObj = Object.fromEntries(
  fruits.entries().map(([index, fruit]) => [fruit, index])
);
console.log(fruitsObj); // { apple: 0, banana: 1, cherry: 2 }

Patrones comunes de búsqueda e iteración

Búsqueda paginada

const allUsers = [
  /* Supongamos una lista de 100 usuarios */
  { id: 1, name: "User 1" },
  { id: 2, name: "User 2" },
  // ...
  { id: 100, name: "User 100" }
];

function getPagedUsers(page, pageSize) {
  const startIndex = (page - 1) * pageSize;
  return allUsers.slice(startIndex, startIndex + pageSize);
}

const page2 = getPagedUsers(2, 10); // Obtiene usuarios 11-20

Búsqueda con múltiples criterios

const employees = [
  { id: 1, name: "Alice", department: "Engineering", salary: 85000 },
  { id: 2, name: "Bob", department: "Marketing", salary: 75000 },
  { id: 3, name: "Charlie", department: "Engineering", salary: 95000 },
  { id: 4, name: "Diana", department: "HR", salary: 65000 },
  { id: 5, name: "Eve", department: "Marketing", salary: 80000 }
];

function searchEmployees(criteria) {
  return employees.filter(employee => {
    // Verificar cada criterio proporcionado
    for (const key in criteria) {
      if (employee[key] !== criteria[key]) {
        return false;
      }
    }
    return true;
  });
}

// Buscar empleados de Marketing
const marketingTeam = searchEmployees({ department: "Marketing" });
console.log(marketingTeam.length); // 2

// Buscar empleados de Engineering con salario específico
const seniorEngineers = searchEmployees({ 
  department: "Engineering", 
  salary: 95000 
});
console.log(seniorEngineers[0].name); // "Charlie"

Búsqueda difusa (fuzzy search)

function fuzzySearch(items, searchTerm) {
  const term = searchTerm.toLowerCase();
  return items.filter(item => {
    const name = item.name.toLowerCase();
    return name.includes(term);
  });
}

const products = [
  { id: 1, name: "iPhone 13" },
  { id: 2, name: "Samsung Galaxy" },
  { id: 3, name: "Google Pixel" },
  { id: 4, name: "iPhone 14 Pro" }
];

const results = fuzzySearch(products, "phone");
console.log(results.length); // 2 (ambos iPhones)

Iteración con acumulación de resultados

const transactions = [
  { id: 1, type: "purchase", amount: 50 },
  { id: 2, type: "sale", amount: 100 },
  { id: 3, type: "purchase", amount: 30 },
  { id: 4, type: "sale", amount: 80 }
];

// Calcular totales por tipo
function calculateTotals(transactions) {
  const totals = {};
  
  transactions.forEach(transaction => {
    const { type, amount } = transaction;
    totals[type] = (totals[type] || 0) + amount;
  });
  
  return totals;
}

console.log(calculateTotals(transactions));
// { purchase: 80, sale: 180 }

Iteración con detención temprana

Aunque forEach no permite detener la iteración, podemos usar otros enfoques:

// Usando for...of (permite break)
function findAndProcess(items, predicate, processor) {
  for (const item of items) {
    if (predicate(item)) {
      processor(item);
      break; // Detener después de encontrar el primer elemento
    }
  }
}

// Usando find (se detiene al encontrar el primer elemento)
function processFirstMatch(items, predicate, processor) {
  const item = items.find(predicate);
  if (item) {
    processor(item);
    return true;
  }
  return false;
}

// Ejemplo de uso
const users = [
  { id: 1, name: "Alice", role: "user" },
  { id: 2, name: "Bob", role: "admin" },
  { id: 3, name: "Charlie", role: "user" }
];

processFirstMatch(
  users,
  user => user.role === "admin",
  admin => console.log(`Found admin: ${admin.name}`)
);
// Output: Found admin: Bob

Optimización de búsquedas

Uso de Map para búsquedas por ID

Para búsquedas frecuentes por ID, convertir un array a un Map puede mejorar significativamente el rendimiento:

const users = [
  { id: 101, name: "Alice" },
  { id: 102, name: "Bob" },
  { id: 103, name: "Charlie" },
  // Imagina miles de usuarios...
];

// Crear un mapa para búsquedas rápidas por ID
const userMap = new Map(users.map(user => [user.id, user]));

// Búsqueda O(1) en lugar de O(n)
function getUser(id) {
  return userMap.get(id);
}

console.log(getUser(102)); // { id: 102, name: "Bob" }

Búsqueda binaria para arrays ordenados

Si el array está ordenado, la búsqueda binaria es mucho más eficiente:

function binarySearch(sortedArray, value) {
  let start = 0;
  let end = sortedArray.length - 1;
  
  while (start <= end) {
    const mid = Math.floor((start + end) / 2);
    
    if (sortedArray[mid] === value) {
      return mid; // Encontrado
    }
    
    if (sortedArray[mid] < value) {
      start = mid + 1; // Buscar en la mitad derecha
    } else {
      end = mid - 1; // Buscar en la mitad izquierda
    }
  }
  
  return -1; // No encontrado
}

const sortedNumbers = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19];
console.log(binarySearch(sortedNumbers, 7));  // 3
console.log(binarySearch(sortedNumbers, 6));  // -1

Memorización para búsquedas repetitivas

Para operaciones de búsqueda costosas que se repiten con los mismos parámetros:

function memoize(fn) {
  const cache = new Map();
  
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

// Función de búsqueda costosa
function findComplexQuery(array, criteria) {
  console.log("Executing expensive search...");
  // Simulamos una búsqueda compleja
  return array.filter(item => {
    // Operación costosa
    return Object.entries(criteria).every(
      ([key, value]) => item[key] === value
    );
  });
}

// Versión memorizada
const memoizedFind = memoize(findComplexQuery);

const data = [
  { id: 1, category: "A", status: "active" },
  { id: 2, category: "B", status: "inactive" },
  { id: 3, category: "A", status: "active" },
  // Imagina miles de elementos...
];

// Primera llamada (ejecuta la búsqueda)
memoizedFind(data, { category: "A", status: "active" });
// Log: "Executing expensive search..."

// Segunda llamada con los mismos parámetros (usa caché)
memoizedFind(data, { category: "A", status: "active" });
// No hay log, usa el resultado en caché

Los métodos de iteración y búsqueda en arrays son herramientas fundamentales en el desarrollo JavaScript moderno. Estos métodos nos permiten escribir código más declarativo, expresivo y mantenible, evitando los bucles tradicionales y centrándose en el "qué" queremos lograr en lugar del "cómo". Dominar estos métodos es esencial para cualquier desarrollador JavaScript que busque escribir código eficiente y elegante.

Técnicas avanzadas y patrones modernos con arrays

Los arrays en JavaScript han evolucionado significativamente con las últimas versiones del lenguaje, permitiendo implementar patrones avanzados y técnicas modernas que mejoran la legibilidad, mantenibilidad y rendimiento del código. Estas técnicas aprovechan tanto las características nativas de JavaScript como los paradigmas de programación funcional para resolver problemas complejos de manera elegante.

Inmutabilidad y operaciones no destructivas

La programación inmutable es un patrón fundamental en el desarrollo moderno de JavaScript, especialmente en frameworks como React o Redux. Consiste en crear nuevas copias de los arrays en lugar de modificar los originales.

1. Operaciones inmutables básicas

// Añadir elementos (inmutable)
const fruits = ["apple", "banana"];
const newFruits = [...fruits, "cherry"];
console.log(newFruits); // ["apple", "banana", "cherry"]
console.log(fruits);    // ["apple", "banana"] - sin cambios

// Eliminar elementos (inmutable)
const numbers = [1, 2, 3, 4, 5];
const withoutThree = numbers.filter(num => num !== 3);
console.log(withoutThree); // [1, 2, 4, 5]
console.log(numbers);      // [1, 2, 3, 4, 5] - sin cambios

// Actualizar elementos (inmutable)
const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" }
];

const updatedUsers = users.map(user => 
  user.id === 2 ? { ...user, name: "Bobby" } : user
);
console.log(updatedUsers[1].name); // "Bobby"
console.log(users[1].name);        // "Bob" - sin cambios

2. Inmutabilidad en operaciones complejas

// Insertar en una posición específica
function insertAt(array, index, item) {
  return [
    ...array.slice(0, index),
    item,
    ...array.slice(index)
  ];
}

const colors = ["red", "green", "blue"];
const newColors = insertAt(colors, 1, "yellow");
console.log(newColors); // ["red", "yellow", "green", "blue"]

// Reemplazar un rango de elementos
function replaceRange(array, start, end, replacement) {
  return [
    ...array.slice(0, start),
    ...replacement,
    ...array.slice(end)
  ];
}

const letters = ["a", "b", "c", "d", "e"];
const newLetters = replaceRange(letters, 1, 3, ["x", "y"]);
console.log(newLetters); // ["a", "x", "y", "d", "e"]

Composición funcional con arrays

La composición funcional permite encadenar operaciones de manera elegante y expresiva, creando flujos de transformación de datos claros y mantenibles.

1. Composición de operaciones

// Transformación de datos en pipeline
const transactions = [
  { id: 1, type: "purchase", amount: 50, status: "completed" },
  { id: 2, type: "refund", amount: 30, status: "pending" },
  { id: 3, type: "purchase", amount: 100, status: "completed" },
  { id: 4, type: "purchase", amount: 20, status: "failed" }
];

// Obtener el total de compras completadas
const totalCompletedPurchases = transactions
  .filter(t => t.type === "purchase")
  .filter(t => t.status === "completed")
  .map(t => t.amount)
  .reduce((sum, amount) => sum + amount, 0);

console.log(totalCompletedPurchases); // 150

2. Funciones de composición personalizadas

// Crear funciones de transformación reutilizables
const filterByType = type => array => array.filter(item => item.type === type);
const filterByStatus = status => array => array.filter(item => item.status === status);
const mapToAmount = array => array.map(item => item.amount);
const sum = array => array.reduce((total, value) => total + value, 0);

// Componer las funciones
const getPurchases = filterByType("purchase");
const getCompleted = filterByStatus("completed");
const calculateTotal = array => sum(mapToAmount(array));

// Usar la composición
const completedPurchasesTotal = calculateTotal(getCompleted(getPurchases(transactions)));
console.log(completedPurchasesTotal); // 150

// Alternativa con composición explícita
function compose(...fns) {
  return x => fns.reduceRight((acc, fn) => fn(acc), x);
}

const calculateCompletedPurchasesTotal = compose(
  sum,
  mapToAmount,
  filterByStatus("completed"),
  filterByType("purchase")
);

console.log(calculateCompletedPurchasesTotal(transactions)); // 150

Patrones de procesamiento por lotes (batch processing)

El procesamiento por lotes permite manejar grandes volúmenes de datos de manera eficiente, dividiendo el trabajo en fragmentos más pequeños.

1. Procesamiento en chunks

// Procesar un array grande en lotes para evitar bloquear el hilo principal
async function processBatch(items, batchSize, processFn) {
  const results = [];
  
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    // Procesar el lote actual
    const batchResults = await processFn(batch);
    results.push(...batchResults);
    
    // Permitir que el hilo principal respire
    await new Promise(resolve => setTimeout(resolve, 0));
  }
  
  return results;
}

// Ejemplo de uso
async function processItems() {
  const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, value: `Item ${i}` }));
  
  const processedItems = await processBatch(items, 500, async batch => {
    console.log(`Processing batch of ${batch.length} items`);
    // Simulación de procesamiento
    return batch.map(item => ({ ...item, processed: true }));
  });
  
  console.log(`Processed ${processedItems.length} items in total`);
}

// processItems();

2. Procesamiento paralelo con Promise.all

// Procesar múltiples lotes en paralelo
async function processInParallel(items, batchSize, processFn, maxConcurrent = 4) {
  const batches = [];
  
  // Dividir en lotes
  for (let i = 0; i < items.length; i += batchSize) {
    batches.push(items.slice(i, i + batchSize));
  }
  
  const results = [];
  
  // Procesar lotes en grupos concurrentes
  for (let i = 0; i < batches.length; i += maxConcurrent) {
    const batchGroup = batches.slice(i, i + maxConcurrent);
    const batchPromises = batchGroup.map(processFn);
    
    const batchResults = await Promise.all(batchPromises);
    results.push(...batchResults.flat());
  }
  
  return results;
}

// Ejemplo de uso
async function simulateApiCalls() {
  const ids = Array.from({ length: 100 }, (_, i) => i + 1);
  
  const fetchBatch = async batch => {
    console.log(`Fetching data for IDs: ${batch.join(', ')}`);
    // Simular llamada a API
    await new Promise(resolve => setTimeout(resolve, 300));
    return batch.map(id => ({ id, data: `Data for ${id}` }));
  };
  
  const results = await processInParallel(ids, 10, fetchBatch, 3);
  console.log(`Fetched data for ${results.length} items`);
}

// simulateApiCalls();

Patrones de transformación avanzados

Estos patrones permiten manipular y transformar estructuras de datos complejas de manera declarativa y eficiente.

1. Normalización de datos

La normalización convierte arrays de objetos anidados en estructuras planas más fáciles de consultar y actualizar:

// Normalizar una estructura de datos anidada
function normalizeByKey(array, key = 'id') {
  return array.reduce((acc, item) => {
    acc.entities[item[key]] = item;
    acc.ids.push(item[key]);
    return acc;
  }, { entities: {}, ids: [] });
}

const posts = [
  { id: 1, title: "First Post", author: { id: 101, name: "Alice" } },
  { id: 2, title: "Second Post", author: { id: 102, name: "Bob" } },
  { id: 3, title: "Third Post", author: { id: 101, name: "Alice" } }
];

const normalizedPosts = normalizeByKey(posts);
console.log(normalizedPosts);
/*
{
  entities: {
    1: { id: 1, title: "First Post", author: { id: 101, name: "Alice" } },
    2: { id: 2, title: "Second Post", author: { id: 102, name: "Bob" } },
    3: { id: 3, title: "Third Post", author: { id: 101, name: "Alice" } }
  },
  ids: [1, 2, 3]
}
*/

// Acceso rápido a un elemento específico
console.log(normalizedPosts.entities[2].title); // "Second Post"

2. Desnormalización de datos

El proceso inverso a la normalización, útil para reconstruir estructuras anidadas:

// Estructura normalizada
const normalizedData = {
  users: {
    entities: {
      101: { id: 101, name: "Alice" },
      102: { id: 102, name: "Bob" }
    },
    ids: [101, 102]
  },
  posts: {
    entities: {
      1: { id: 1, title: "Hello", authorId: 101 },
      2: { id: 2, title: "World", authorId: 102 },
      3: { id: 3, title: "Again", authorId: 101 }
    },
    ids: [1, 2, 3]
  }
};

// Desnormalizar posts con sus autores
function denormalizePosts(normalizedData) {
  const { users, posts } = normalizedData;
  
  return posts.ids.map(id => {
    const post = posts.entities[id];
    const author = users.entities[post.authorId];
    
    return {
      ...post,
      author: { ...author }
    };
  });
}

const denormalizedPosts = denormalizePosts(normalizedData);
console.log(denormalizedPosts[0]);
// { id: 1, title: "Hello", authorId: 101, author: { id: 101, name: "Alice" } }

3. Transformación de árboles

Manipulación de estructuras de datos jerárquicas:

// Estructura de árbol (por ejemplo, categorías anidadas)
const categories = [
  { id: 1, name: "Electronics", parentId: null },
  { id: 2, name: "Computers", parentId: 1 },
  { id: 3, name: "Laptops", parentId: 2 },
  { id: 4, name: "Smartphones", parentId: 1 },
  { id: 5, name: "Accessories", parentId: 1 },
  { id: 6, name: "Headphones", parentId: 5 }
];

// Convertir lista plana a estructura de árbol
function listToTree(items, idKey = 'id', parentKey = 'parentId', childrenKey = 'children') {
  const map = {};
  const roots = [];
  
  // Crear mapa de elementos por ID
  items.forEach(item => {
    map[item[idKey]] = { ...item, [childrenKey]: [] };
  });
  
  // Asignar hijos a padres
  items.forEach(item => {
    const id = item[idKey];
    const parentId = item[parentKey];
    const node = map[id];
    
    if (parentId === null) {
      // Es un nodo raíz
      roots.push(node);
    } else if (map[parentId]) {
      // Añadir como hijo del padre
      map[parentId][childrenKey].push(node);
    }
  });
  
  return roots;
}

const categoryTree = listToTree(categories);
console.log(JSON.stringify(categoryTree, null, 2));
/*
[
  {
    "id": 1,
    "name": "Electronics",
    "parentId": null,
    "children": [
      {
        "id": 2,
        "name": "Computers",
        "parentId": 1,
        "children": [
          {
            "id": 3,
            "name": "Laptops",
            "parentId": 2,
            "children": []
          }
        ]
      },
      {
        "id": 4,
        "name": "Smartphones",
        "parentId": 1,
        "children": []
      },
      {
        "id": 5,
        "name": "Accessories",
        "parentId": 1,
        "children": [
          {
            "id": 6,
            "name": "Headphones",
            "parentId": 5,
            "children": []
          }
        ]
      }
    ]
  }
]
*/

Patrones de optimización de rendimiento

Estos patrones mejoran el rendimiento al trabajar con grandes volúmenes de datos o operaciones frecuentes.

1. Memorización de resultados

// Función para memorizar resultados de operaciones costosas
function memoizeArray(fn) {
  const cache = new Map();
  
  return function(array, ...args) {
    // Crear una clave única basada en el contenido del array y los argumentos
    const key = JSON.stringify([array, args]);
    
    if (cache.has(key)) {
      return cache.get(key);
    }
    
    const result = fn(array, ...args);
    cache.set(key, result);
    return result;
  };
}

// Función costosa: encontrar todos los pares que suman un valor específico
const findPairs = (numbers, targetSum) => {
  console.log("Calculating pairs...");
  const result = [];
  
  for (let i = 0; i < numbers.length; i++) {
    for (let j = i + 1; j < numbers.length; j++) {
      if (numbers[i] + numbers[j] === targetSum) {
        result.push([numbers[i], numbers[j]]);
      }
    }
  }
  
  return result;
};

// Versión memorizada
const memoizedFindPairs = memoizeArray(findPairs);

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

console.log(memoizedFindPairs(numbers, 10)); // Calcula y guarda en caché
// Calculating pairs...
// [[1, 9], [2, 8], [3, 7], [4, 6]]

console.log(memoizedFindPairs(numbers, 10)); // Usa la versión en caché
// [[1, 9], [2, 8], [3, 7], [4, 6]]

2. Indexación para búsquedas frecuentes

// Crear índices para búsquedas rápidas en colecciones grandes
function createIndexedCollection(items, indexFields = []) {
  const collection = {
    items: [...items],
    indexes: {},
    
    // Crear índices
    buildIndexes() {
      indexFields.forEach(field => {
        this.indexes[field] = new Map();
        
        this.items.forEach((item, idx) => {
          const value = item[field];
          
          if (!this.indexes[field].has(value)) {
            this.indexes[field].set(value, []);
          }
          
          this.indexes[field].get(value).push(idx);
        });
      });
      
      return this;
    },
    
    // Buscar por un campo indexado
    findBy(field, value) {
      if (!this.indexes[field]) {
        throw new Error(`No index for field: ${field}`);
      }
      
      const indexes = this.indexes[field].get(value) || [];
      return indexes.map(idx => this.items[idx]);
    },
    
    // Añadir un nuevo elemento y actualizar índices
    add(item) {
      const idx = this.items.length;
      this.items.push(item);
      
      // Actualizar índices
      indexFields.forEach(field => {
        const value = item[field];
        
        if (!this.indexes[field].has(value)) {
          this.indexes[field].set(value, []);
        }
        
        this.indexes[field].get(value).push(idx);
      });
      
      return this;
    }
  };
  
  return collection.buildIndexes();
}

// Ejemplo de uso
const users = [
  { id: 1, name: "Alice", role: "admin", department: "IT" },
  { id: 2, name: "Bob", role: "user", department: "Sales" },
  { id: 3, name: "Charlie", role: "user", department: "IT" },
  { id: 4, name: "Diana", role: "manager", department: "Marketing" },
  { id: 5, name: "Eve", role: "user", department: "Sales" }
];

const indexedUsers = createIndexedCollection(users, ["role", "department"]);

// Búsquedas rápidas O(1) en lugar de O(n)
console.log(indexedUsers.findBy("role", "user").length); // 3
console.log(indexedUsers.findBy("department", "IT").length); // 2

// Añadir un nuevo usuario
indexedUsers.add({ id: 6, name: "Frank", role: "user", department: "IT" });
console.log(indexedUsers.findBy("department", "IT").length); // 3

Patrones de manipulación de datos en tiempo real

Estos patrones son útiles para aplicaciones que necesitan mantener y actualizar colecciones de datos en tiempo real.

1. Gestión de colecciones reactivas

// Implementación simple de una colección reactiva
function createReactiveCollection(initialItems = []) {
  let items = [...initialItems];
  const listeners = new Set();
  
  return {
    // Obtener todos los elementos
    getAll() {
      return [...items]; // Devolver copia para preservar inmutabilidad
    },
    
    // Añadir un elemento
    add(item) {
      items.push(item);
      this.notify();
      return this;
    },
    
    // Actualizar un elemento
    update(id, updater) {
      items = items.map(item => 
        item.id === id ? { ...item, ...updater(item) } : item
      );
      this.notify();
      return this;
    },
    
    // Eliminar un elemento
    remove(id) {
      items = items.filter(item => item.id !== id);
      this.notify();
      return this;
    },
    
    // Filtrar elementos (sin modificar la colección)
    filter(predicate) {
      return items.filter(predicate);
    },
    
    // Suscribirse a cambios
    subscribe(callback) {
      listeners.add(callback);
      
      // Devolver función para cancelar suscripción
      return () => {
        listeners.delete(callback);
      };
    },
    
    // Notificar a todos los suscriptores
    notify() {
      const currentItems = this.getAll();
      listeners.forEach(listener => listener(currentItems));
    }
  };
}

// Ejemplo de uso
const todoCollection = createReactiveCollection([
  { id: 1, text: "Learn JavaScript", completed: true },
  { id: 2, text: "Learn React", completed: false }
]);

// Suscribirse a cambios
const unsubscribe = todoCollection.subscribe(todos => {
  console.log("Todos updated:", todos.length);
});

// Realizar cambios
todoCollection.add({ id: 3, text: "Learn TypeScript", completed: false });
// Todos updated: 3

todoCollection.update(2, todo => ({ completed: true }));
// Todos updated: 3

todoCollection.remove(1);
// Todos updated: 2

// Cancelar suscripción
unsubscribe();

// Este cambio no generará log porque cancelamos la suscripción
todoCollection.add({ id: 4, text: "Learn Node.js", completed: false });

2. Sincronización de cambios con diff

// Calcular diferencias entre dos arrays
function arrayDiff(oldArray, newArray, idKey = 'id') {
  const oldMap = new Map(oldArray.map(item => [item[idKey], item]));
  const newMap = new Map(newArray.map(item => [item[idKey], item]));
  
  const added = newArray.filter(item => !oldMap.has(item[idKey]));
  const removed = oldArray.filter(item => !newMap.has(item[idKey]));
  
  const potentiallyModified = oldArray.filter(item => 
    newMap.has(item[idKey]) && item !== newMap.get(item[idKey])
  );
  
  const modified = potentiallyModified.filter(oldItem => {
    const newItem = newMap.get(oldItem[idKey]);
    return JSON.stringify(oldItem) !== JSON.stringify(newItem);
  });
  
  return {
    added,
    removed,
    modified: modified.map(oldItem => ({
      old: oldItem,
      new: newMap.get(oldItem[idKey])
    }))
  };
}

// Ejemplo de uso
const oldUsers = [
  { id: 1, name: "Alice", role: "admin" },
  { id: 2, name: "Bob", role: "user" },
  { id: 3, name: "Charlie", role: "user" }
];

const newUsers = [
  { id: 1, name: "Alice", role: "admin" },
  { id: 2, name: "Bob", role: "manager" }, // Rol modificado
  { id: 4, name: "Diana", role: "user" }   // Usuario nuevo
  // Charlie eliminado
];

const changes = arrayDiff(oldUsers, newUsers);
console.log(changes);
/*
{
  added: [{ id: 4, name: "Diana", role: "user" }],
  removed: [{ id: 3, name: "Charlie", role: "user" }],
  modified: [{
    old: { id: 2, name: "Bob", role: "user" },
    new: { id: 2, name: "Bob", role: "manager" }
  }]
}
*/

// Aplicar cambios de forma eficiente
function applyChanges(array, changes, idKey = 'id') {
  // Crear mapa para búsquedas rápidas
  const itemMap = new Map(array.map(item => [item[idKey], item]));
  
  // Aplicar modificaciones
  changes.modified.forEach(({ old, new: newItem }) => {
    const id = old[idKey];
    if (itemMap.has(id)) {
      itemMap.set(id, { ...itemMap.get(id), ...newItem });
    }
  });
  
  // Eliminar elementos
  changes.removed.forEach(item => {
    itemMap.delete(item[idKey]);
  });
  
  // Añadir nuevos elementos
  changes.added.forEach(item => {
    itemMap.set(item[idKey], item);
  });
  
  // Convertir mapa de vuelta a array
  return Array.from(itemMap.values());
}

Patrones de validación y transformación de datos

Estos patrones garantizan la integridad y consistencia de los datos.

1. Validación de esquemas

// Validador de esquema simple
function validateSchema(data, schema) {
  const errors = [];
  
  Object.entries(schema).forEach(([field, rules]) => {
    const value = data[field];
    
    // Verificar si el campo es requerido
    if (rules.required && (value === undefined || value === null || value === '')) {
      errors.push(`${field} is required`);
      return;
    }
    
    // Si el campo no existe y no es requerido, omitir validaciones
    if (value === undefined) return;
    
    // Validar tipo
    if (rules.type && typeof value !== rules.type) {
      errors.push(`${field} must be of type ${rules.type}`);
    }
    
    // Validar longitud mínima (para strings y arrays)
    if (rules.minLength !== undefined && value.length < rules.minLength) {
      errors.push(`${field} must have at least ${rules.minLength} characters`);
    }
    
    // Validar longitud máxima (para strings y arrays)
    if (rules.maxLength !== undefined && value.length > rules.maxLength) {
      errors.push(`${field} must have at most ${rules.maxLength} characters`);
    }
    
    // Validar valor mínimo (para números)
    if (rules.min !== undefined && value < rules.min) {
      errors.push(`${field} must be at least ${rules.min}`);
    }
    
    // Validar valor máximo (para números)
    if (rules.max !== undefined && value > rules.max) {
      errors.push(`${field} must be at most ${rules.max}`);
    }
    
    // Validar con expresión regular
    if (rules.pattern && !rules.pattern.test(value)) {
      errors.push(`${field} has invalid format`);
    }
    
    // Validar con función personalizada
    if (rules.validate && typeof rules.validate === 'function') {
      const validationResult = rules.validate(value, data);
      if (validationResult !== true) {
        errors.push(validationResult || `${field} is invalid`);
      }
    }
  });
  
  return {
    valid: errors.length === 0,
    errors
  };
}

// Ejemplo de uso
const userSchema = {
  name: { required: true, type: 'string', minLength: 2 },
  email: { 
    required: true, 
    pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    validate: (email) => email.includes('@') || 'Invalid email format'
  },
  age: { type: 'number', min: 18, max: 120 },
  roles: { type: 'object', validate: (roles) => Array.isArray(roles) || 'Roles must be an array' }
};

const user = {
  name: 'A',
  email: 'invalid-email',
  age: 16,
  roles: 'admin' // Debería ser un array
};

const validation = validateSchema(user, userSchema);
console.log(validation.valid); // false
console.log(validation.errors);
// [
//   "name must have at least 2 characters",
//   "Invalid email format",
//   "age must be at least 18",
//   "Roles must be an array"
// ]

2. Transformación y normalización de datos

// Transformador de datos basado en esquema
function transformData(data, transformers) {
  const result = { ...data };
  
  Object.entries(transformers).forEach(([field, transformer]) => {
    if (result[field] !== undefined) {
      if (typeof transformer === 'function') {
        result[field] = transformer(result[field], result);
      } else if (transformer.transform) {
        result[field] = transformer.transform(result[field], result);
      }
    } else if (transformer.default !== undefined) {
      result[field] = typeof transformer.default === 'function' 
        ? transformer.default(result)
        : transformer.default;
    }
  });
  
  return result;
}

// Ejemplo de uso
const userTransformers = {
  name: value => value.trim(),
  email: value => value.toLowerCase().trim(),
  age: {
    transform: value => Number(value),
    default: 18
  },
  createdAt: {
    default: () => new Date().toISOString()
  },
  roles: {
    transform: value => Array.isArray(value) ? value : [value],
    default: ['user']
  }
};

const userData = {
  name: '  John Doe  ',
  email: '  JOHN@EXAMPLE.COM  ',
  age: '25',
  roles: 'editor'
};

const transformedUser = transformData(userData, userTransformers);
console.log(transformedUser);
/*
{
  name: 'John Doe',
  email: 'john@example.com',
  age: 25,
  roles: ['editor'],
  createdAt: '2023-05-15T14:30:45.123Z' (ejemplo)
}
*/

Patrones de manejo de estado con arrays

Estos patrones son útiles para implementar sistemas de gestión de estado, especialmente en aplicaciones frontend.

1. Implementación de un historial de acciones (undo/redo)

// Gestor de historial simple
function createHistoryManager(initialState) {
  let currentIndex = 0;
  const states = [initialState];
  
  return {
    // Obtener estado actual
    getCurrentState() {
      return states[currentIndex];
    },
    
    // Aplicar una nueva acción
    applyAction(action) {
      // Eliminar estados futuros si estamos en medio del historial
      if (currentIndex < states.length - 1) {
        states.splice(currentIndex + 1);
      }
      
      // Calcular y guardar nuevo estado
      const currentState = this.getCurrentState();
      const newState = action(currentState);
      states.push(newState);
      currentIndex++;
      
      return newState;
    },
    
    // Deshacer última acción
    undo() {
      if (currentIndex > 0) {
        currentIndex--;
        return this.getCurrentState();
      }
      return this.getCurrentState();
    },
    
    // Rehacer acción deshecha
    redo() {
      if (currentIndex < states.length - 1) {
        currentIndex++;
        return this.getCurrentState();
      }
      return this.getCurrentState();
    },
    
    // Verificar si podemos deshacer
    canUndo() {
      return currentIndex > 0;
    },
    
    // Verificar si podemos rehacer
    canRedo() {
      return currentIndex < states.length - 1;
    },
    
    // Obtener historial completo
    getHistory() {
      return {
        states: [...states],
        currentIndex
      };
    }
  };
}

// Ejemplo de uso con un editor de texto simple
const initialDocument = {
  title: "Untitled",
  content: [],
  selectedIndex: -1
};

const documentHistory = createHistoryManager(initialDocument);

// Añadir párrafo
const addParagraph = documentHistory.applyAction(doc => ({
  ...doc,
  content: [...doc.content, "New paragraph"],
  selectedIndex: doc.content.length
}));

console.log(addParagraph.content); // ["New paragraph"]

// Editar párrafo
const editParagraph = documentHistory.applyAction(doc => ({
  ...doc,
  content: doc.content.map((p, i) => 
    i === doc.selectedIndex ? "Edited paragraph" : p
  )
}));

console.log(editParagraph.content); // ["Edited paragraph"]

// Deshacer (volver a "New paragraph")
const undoneState = documentHistory.undo();
console.log(undoneState.content); // ["New paragraph"]

// Rehacer (volver a "Edited paragraph")
const redoneState = documentHistory.redo();
console.log(redoneState.content); // ["Edited paragraph"]

2. Implementación de un store con suscripciones

// Store simple inspirado en Redux
function createStore(reducer, initialState) {
  let state = initialState;
  const listeners = [];
  
  function getState() {
    return state;
  }
  
  function dispatch(action) {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
    return action;
  }
  
  function subscribe(listener) {
    listeners.push(listener);
    return function unsubscribe() {
      const index = listeners.indexOf(listener);
      if (index !== -1) {
        listeners.splice(index, 1);
      }
    };
  }
  
  // Inicializar el store
  dispatch({ type: '@@INIT' });
  
  return { getState, dispatch, subscribe };
}

// Ejemplo de uso con una lista de tareas
const initialState = {
  todos: [],
  filter: 'all' // 'all', 'active', 'completed'
};

// Reducer
function todosReducer(state = initialState, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, {
          id: Date.now(),
          text: action.text,
          completed: false
        }]
      };
      
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
        )
      };
      
    case 'SET_FILTER':
      return {
        ...state,
        filter: action.filter
      };
      
    default:
      return state;
  }
}

// Crear store
const store = createStore(todosReducer, initialState);

// Suscribirse a cambios
const unsubscribe = store.subscribe(() => {
  console.log('State updated:', store.getState());
});

// Despachar acciones
store.dispatch({ type: 'ADD_TODO', text: 'Learn JavaScript' });
// State updated: { todos: [{ id: 1621234567890, text: 'Learn JavaScript', completed: false }], filter: 'all' }

store.dispatch({ type: 'ADD_TODO', text: 'Learn React' });
// State updated: { todos: [{ id: 1621234567890, text: 'Learn JavaScript', completed: false }, { id: 1621234567891, text: 'Learn React', completed: false }], filter: 'all' }

store.dispatch({ type: 'TOGGLE_TODO', id: 1621234567890 });
// State updated: { todos: [{ id: 1621234567890, text: 'Learn JavaScript', completed: true }, { id: 1621234567891, text: 'Learn React', completed: false }], filter: 'all' }

// Cancelar suscripción
unsubscribe();

Los patrones y técnicas avanzadas presentados en esta sección representan prácticas modernas para trabajar con arrays en JavaScript. Estos enfoques aprovechan las características más recientes del lenguaje y los paradigmas de programación funcional para crear código más mantenible, eficiente y expresivo. Dominar estas técnicas te permitirá abordar problemas complejos de manipulación de datos con soluciones elegantes y escalables.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € al año
Precio normal anual: 120 €
Aprende JavaScript online

Ejercicios de esta lección Arrays y Métodos

Evalúa tus conocimientos de esta lección Arrays y Métodos con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Funciones flecha

JavaScript
Puzzle

Polimorfismo

JavaScript
Test

Array

JavaScript
Código

Transformación con map()

JavaScript
Test

Gestor de tareas con JavaScript

JavaScript
Proyecto

Manipulación DOM

JavaScript
Test

Funciones

JavaScript
Test

Funciones flecha

JavaScript
Código

Async / Await

JavaScript
Código

Creación y uso de variables

JavaScript
Test

Excepciones

JavaScript
Puzzle

Promises

JavaScript
Código

Funciones cierre (closure)

JavaScript
Test

Herencia

JavaScript
Puzzle

Herencia

JavaScript
Test

Estructuras de control

JavaScript
Código

Selección de elementos DOM

JavaScript
Test

Modificación de elementos DOM

JavaScript
Test

Filtrado con filter() y find()

JavaScript
Test

Funciones cierre (closure)

JavaScript
Puzzle

Funciones

JavaScript
Puzzle

Mapas con Map

JavaScript
Test

Reducción con reduce()

JavaScript
Test

Callbacks

JavaScript
Puzzle

Manipulación DOM

JavaScript
Puzzle

Promises

JavaScript
Test

Async / Await

JavaScript
Test

Eventos del DOM

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Puzzle

Async / Await

JavaScript
Puzzle

Promises

JavaScript
Puzzle

Filtrado con filter() y find()

JavaScript
Código

Callbacks

JavaScript
Test

Creación de clases y objetos Restaurante

JavaScript
Código

Reducción con reduce()

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Puzzle

Reducción con reduce()

JavaScript
Puzzle

Conjuntos con Set

JavaScript
Puzzle

Herencia de clases

JavaScript
Código

Eventos del DOM

JavaScript
Test

Clases y objetos

JavaScript
Puzzle

Modificación de elementos DOM

JavaScript
Puzzle

Mapas con Map

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Test

Funciones

JavaScript
Código

Tipos de datos

JavaScript
Test

Clases y objetos

JavaScript
Test

Array

JavaScript
Test

Conjuntos con Set

JavaScript
Test

Array

JavaScript
Puzzle

Encapsulación

JavaScript
Puzzle

Clases y objetos

JavaScript
Código

Uso de operadores

JavaScript
Puzzle

Uso de operadores

JavaScript
Test

Estructuras de control

JavaScript
Test

Excepciones

JavaScript
Test

Transformación con map()

JavaScript
Puzzle

Funciones flecha

JavaScript
Test

Selección de elementos DOM

JavaScript
Puzzle

Encapsulación

JavaScript
Test

Mapas con Map

JavaScript
Código

Creación y uso de variables

JavaScript
Puzzle

Polimorfismo

JavaScript
Puzzle

Tipos de datos

JavaScript
Puzzle

Estructuras de control

JavaScript
Puzzle

Todas las lecciones de JavaScript

Accede a todas las lecciones de JavaScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a JavaScript y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender qué es un Array en JavaScript y su propósito como estructura de datos.
  2. Conocer las diferentes formas de crear Arrays utilizando el constructor Array y la notación de arreglo literal.
  3. Aprender a acceder y modificar elementos en un Array mediante índices y métodos como push, pop, splice, join, slice, concat, reverse y sort.
  4. Entender las propiedades y métodos heredados por los Arrays del prototipo Array.prototype, como length, indexOf, splice, join, slice, concat, reverse y sort.
  5. Familiarizarse con la naturaleza dinámica y heterogénea de los Arrays en JavaScript, lo que permite agregar o eliminar elementos y almacenar diferentes tipos de datos en el mismo Array.
  6. Reconocer la importancia de los Arrays como estructura fundamental para organizar y manipular colecciones de datos en JavaScript.