limpieza

Funciones de Array en Javascript

En nuestro día a día tenemos que manipular frecuentemente objetos, Strings, Arrays, etc… En esta entrada vamos a echar un vistazo a algunas funciones de Array en Javascript y vamos a ver cómo nos pueden ayudar a dejar nuestro código más limpio y un poco más “entendible” para nuestros compañeros.

Como ya conoceréis, para casi todos los casos en los que tengáis que recorrer un Array, tenemos varias alternativas disponibles. Por ejemplo, si queremos buscar un elemento dentro de un Array podemos usar un bucle while o un for:

let personas = [
    { nombre: 'nombre1', edad: 28 }, 
    { nombre: 'nombre2', edad: 30 },
    { nombre: 'nombre3', edad: 32 }
];

// Ejemplo con While 
let i = 0;
let personaBuscada = null;

while (personaBuscada === null) {
    if (personas[i].edad === 28) {
        personaBuscada  = personas[i];
    }
    i++;
}

// El mismo ejemplo con For 
let personaBuscada = null;
for (var i = 0; i < personas.length; i++) {
    if (personas[i].edad === 28) { 
        personaBuscada = personas[i]; 
        break;
    }
}

Dejando de lado cuestiones de rendimiento, que en la mayoría de casos no son significativas, la principal diferencia está en la legibilidad del código.

Los dos recorren un array hasta que encuentran una persona con 28 años de edad y después salen del bucle. Si no se nos ocurre ningún motivo para utilizar uno u otro, tenemos que ponernos en el lugar de la persona que vaya a leer el programa en el futuro.

Cuando encontramos un bucle while sabemos que va a recorrer el Array hasta que se cumpla una o varias condiciones, mientras que si encontramos un for, esperamos que recorra todos los elementos que haya desde el inicio hasta el final aunque, como se puede ver en este caso (hay un break), no siempre es así.

En este caso, podemos suponer que nuestro compañero va a entender mejor nuestra intención de buscar un elemento si utilizamos un bucle while en lugar de un for.

Cuanto más simple mejor

Los problemas que resuelve nuestro programa pueden ser complejos, pero nuestro código tiene que ser siempre lo más simple posible para no convertirse en parte del problema.

Podríamos utilizar siempre el mismo tipo de bucle y nuestro código funcionaría igual, pero a medida que el programa evolucione y se complique, aparecerán nuevas funciones, bucles enrevesados y necesitamos mantener el código lo más legible y ordenado posible.

Pensad que aproximadamente el 90% del tiempo que se dedique a nuestro proyecto será para mantenerlo, así que podemos mantenerlo simple (“keep it simple, stupid” o KISS) o esperar a que nuestros compañeros empiecen a maldecir cuando revisen nuestro código.

Posible escenario si no usamos funciones de array y complicamos el código máss de la cuenta

Una cosa que podemos hacer para hacer nuestro código más simple es utilizar las funciones de array, cada una en el caso que más encaje.

Las funciones de Array están disponibles en todos los objetos de tipo Array. Para poder utilizarlas basta con crear un Array y acceder a ellas, por ejemplo:

let personas = [ 
{ nombre: 'nombre1', edad: 28 }, 
{ nombre: 'nombre2', edad: 30 }, 
{ nombre: 'nombre3', edad: 32 } 
];

personas.forEach((persona)=>{})

Veamos algunas de estas funciones :

forEach

La primera de estas funciones la utilizaremos cuando queramos hacer algo con cada elemento del array.

Es útil cuando queremos recorrer todos los elementos y hacer cualquier operación con cada uno de ellos por separado. Por ejemplo, queremos mostrar todos los elementos por la consola:

let personas = ['nombre1', 'nombre2', 'nombre3'];

personas.forEach((persona) => {
    console.log(persona);
})

find

Queremos buscar un elemento y recogerlo en una variable.
Si quisieramos obtener un array con todos los elementos que cumplan esta condición, deberíamos utilizar filter.
Un ejemplo con find, queremos buscar la primera persona que tenga 30 años o más:

let personas = [ 
    { nombre: 'nombre1', edad: 28 },
    { nombre: 'nombre2', edad: 30 },
    { nombre: 'nombre3', edad: 32 } 
];

let personaEncontrada = personas.find((persona) => {
    return persona.edad >= 30;
});
// personaEncontrada es { nombre: 'nombre2', edad: 30 }

filter

Queremos un array filtrando elementos de otro array.
Por ejemplo, queremos buscar todas las personas que tengan 30 años o más:

let personas = [ 
    { nombre: 'nombre1', edad: 28 },
    { nombre: 'nombre2', edad: 30 },
    { nombre: 'nombre3', edad: 32 } 
];

let personaEncontrada = personas.find((persona) => {
    return persona.edad >= 30;
});
// personaEncontrada es { nombre: 'nombre2', edad: 30 }
Cuidado con los objetos Cuando un array está compuesto por objetos, si la función nos devuelve un elemento, este se pasa como referencia. En este ejemplo, si modificásemos el nombre de un elemento, este también cambiaría en el array original. Es un pequeño detalle que hay que tener en cuenta, porque os puede ahorrar más de un bug, o dos

some

Queremos comprobar que al menos un elemento cumple la condición. Devuelve true o false dependiendo de si ha encontrado el elemento o no.
Comprobamos si hay alguna persona mayor de 40 años :

let personas = [ 
    { nombre: 'nombre1', edad: 28 },
    { nombre: 'nombre2', edad: 30 },
    { nombre: 'nombre3', edad: 32 } 
];

let dentroDelRango = personas.some((persona) => {
    return persona.edad > 40;
});
// dentroDelRango es false

Mismo ejemplo para comprobar si hay al menos una persona menor de 29 años:

let dentroDelRango = personas.some((persona) => {
    return persona.edad < 29;
});
// dentroDelRango es true

every

Queremos comprobar que todos los elementos cumplen con las condiciones que queramos. Si encuentra un elemento que NO cumple con la condición que queremos, sale del bucle. Devuelve true si cumplen la condición y false en caso contrario.
Comprobamos si todas las personas son mayores de 30 años :

let personas = [ 
    { nombre: 'nombre1', edad: 28 },
    { nombre: 'nombre2', edad: 30 },
    { nombre: 'nombre3', edad: 32 } 
];

var dentroDelRango = personas.every((persona) => {
    return persona.edad > 30;
});
// dentroDelRango es false

Comprobamos que todos son menores de 40:

let dentroDelRango = personas.every((persona) => {
    return persona.edad < 40;
});
// dentroDelRango es true

map

Queremos crear un nuevo array aplicando una función que transforme todos los elemento del array original y manteniendo el original intacto.
Imaginad que queremos quitar la palabra ‘nombre’ en la propiedad nombre de cada persona :

let personas = ['nombre1', 'nombre2', 'nombre3'];

let ids = personas.map((persona) => {
   return  persona.nombre.replace('nombre', '')
});
// ids es ['1', '2', '3']
Si map es un array de objetos y la función hace return de ese mismo objeto, estaréis creando un nuevo array con referencias a los objetos del array original y cualquier cambio en uno de estos objetos se verá en los dos arrays

reduce

Esta función puede resultar un poco más difícil de entender, pero es realmente útil.
Con ella, vamos a acumular el array en un solo elemento. En ciertosEsta es la estructura

arrayOrigen.reduce(funcionCallback, valorInicial);

En la funcionCallback es donde metemos la lógica para acumular los valores y valorInicial es el valor inicial del total.

Con ejemplos se ve mejor

Por ejemplo, supongamos que tenemos un array de números y queremos acumularlos en un solo valor (una suma normal, con valor inicial 0):

let numeros = [299, 100, 350];

const suma = numeros.reduce((total, numero, indice, array)=> {
    return total + numero;
}, 0);
// suma es 749

En cada vuelta la función callback de reduce recibe 4 parámetros Total es el valor acumulado hasta ahora, numero es el elemento del array que estamos tratando en esta vuelta, indice es la posición de ese elemento en el array y por último el array original. Los dos primeros son imprescindibles y el resto opcionales.

En este ejemplo, en la primera vuelta:
total: 0, numero: 299

Segunda vuelta:
total: 299, numero: 100

Última vuelta:
total: 399, numero: 350

Y la última vuelta devolverá 399+350 que es el resultado final 749

Si no ponemos valor inicial, la primera vuelta cogerá el primer elemento del array como total:

Primera vuelta sin valor inicial:
total: 299, numero: 100

Última vuelta:
total: 399, numero: 350

En este caso ahorramos una vuelta, pero en los casos en los que queramos acumular el resultado en un nuevo array, el valor inicial es obligatorio.

También podríamos calcular la media de estos números devolviendo en la última vuelta el valor total dividido entre la longitud del array:

let numeros = [299, 100, 350]; 
const media = numeros.reduce((total, numero, indice, array)=> {
    total += numero;
    if (indice === array.length-1) {
        return total/array.length
    }
    return total; 
}, 0);
// media es 249.66666...

También podemos crear un nuevo array modificando el original y filtrando a la vez. Como si aplicásemos a la vez las funciones map y filter. Ejemplo, queremos hacer un descuento del 30% en los productos que tengan 100 unidades o menos :

let productos = [
	{ nombre: 'prod1', precio: 299, unidades: 1000 },
	{ nombre: 'prod2', precio: 100, unidades: 34 },
	{ nombre: 'prod3', precio: 1200, unidades: 134 },
	{ nombre: 'prod4', precio: 770, unidades: 83 },
	{ nombre: 'prod5', precio: 350, unidades: 46 }
];
let productosPromocionados = productos.reduce((arrayAcumulador, producto) => {
    if (producto.unidades <= 100) {
        producto.precio = producto.precio * 0.7;
        arrayAcumulador.push(producto);
    }
    return arrayAcumulador;
}, []);
// productosPromocionados contiene solo los productos a los que hemos aplicado el descuento con el precio cambiado 
// CUIDADO: si os fijáis, hemos modificado propiedades en los objetos originales, con lo que el array original también estará modificado. 
// Si queremos mantener el array original intacto, tenemos que copiar el objeto original antes de modificarlo y añadirlo al arrayAcumulador. 
// Quedaría así: 
let productosPromocionados = productos.reduce((arrayAcumulador, producto) => {
    if (producto.unidades <= 100) {
        let copiaProducto = JSON.parse(JSON.stringify(producto));
        copiaProducto.precio = copiaProducto.precio * 0.7;
        arrayAcumulador.push(copiaProducto);
    }
    return arrayAcumulador;
}, []);

Nos será útil si tenemos que filtrar y modificar a la vez, ya que hacemos las dos operaciones recorriendo una sola vez toda la colección y lo agradeceremos cuando la colección ser muy grande.
Si tuviésemos que hacer solo una de las operaciones es mejor utilizar filter o map por separado, recordad que queremos que se entiendan nuestras intenciones.

Acabando

Evidentemente, hay otras funciones de Array en Javascript, pero no tampoco podemos abarcar todas de una tacada. Si empezamos a utilizar estas poco a poco, al final las utilizaremos de forma automática y podremos enfocarnos en otros puntos de mejora.

Espero que os sea útil