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ícateCreació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
ycontinue
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.
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
Polimorfismo
Array
Transformación con map()
Gestor de tareas con JavaScript
Manipulación DOM
Funciones
Funciones flecha
Async / Await
Creación y uso de variables
Excepciones
Promises
Funciones cierre (closure)
Herencia
Herencia
Estructuras de control
Selección de elementos DOM
Modificación de elementos DOM
Filtrado con filter() y find()
Funciones cierre (closure)
Funciones
Mapas con Map
Reducción con reduce()
Callbacks
Manipulación DOM
Promises
Async / Await
Eventos del DOM
Introducción a JavaScript
Async / Await
Promises
Filtrado con filter() y find()
Callbacks
Creación de clases y objetos Restaurante
Reducción con reduce()
Filtrado con filter() y find()
Reducción con reduce()
Conjuntos con Set
Herencia de clases
Eventos del DOM
Clases y objetos
Modificación de elementos DOM
Mapas con Map
Introducción a JavaScript
Funciones
Tipos de datos
Clases y objetos
Array
Conjuntos con Set
Array
Encapsulación
Clases y objetos
Uso de operadores
Uso de operadores
Estructuras de control
Excepciones
Transformación con map()
Funciones flecha
Selección de elementos DOM
Encapsulación
Mapas con Map
Creación y uso de variables
Polimorfismo
Tipos de datos
Estructuras de control
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.
Introducción A Javascript
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Funciones Cierre (Closure)
Sintaxis
Arrays Y Métodos
Estructuras De Datos
Conjuntos Con Set
Estructuras De Datos
Mapas Con Map
Estructuras De Datos
Funciones Flecha
Programación Funcional
Filtrado Con Filter() Y Find()
Programación Funcional
Transformación Con Map()
Programación Funcional
Reducción Con Reduce()
Programación Funcional
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Manipulación Dom
Dom
Selección De Elementos Dom
Dom
Modificación De Elementos Dom
Dom
Eventos Del Dom
Dom
Callbacks
Programación Asíncrona
Promises
Programación Asíncrona
Async / Await
Programación Asíncrona
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender qué es un Array en JavaScript y su propósito como estructura de datos.
- Conocer las diferentes formas de crear Arrays utilizando el constructor
Array
y la notación de arreglo literal. - Aprender a acceder y modificar elementos en un Array mediante índices y métodos como
push
,pop
,splice
,join
,slice
,concat
,reverse
ysort
. - Entender las propiedades y métodos heredados por los Arrays del prototipo
Array.prototype
, comolength
,indexOf
,splice
,join
,slice
,concat
,reverse
ysort
. - 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.
- Reconocer la importancia de los Arrays como estructura fundamental para organizar y manipular colecciones de datos en JavaScript.