Nell'elaborare una sequenza di dati più o meno lunga JavaScript ci offre
delle possibilità per poterlo fare in maniera più comoda e magari anche più intuitiva,
quelle che vedremo sono tutte operazioni che si possono fare con le classiche strutture
per il controllo del flusso (if e for ad esempio) ma che con i metodi dell'oggetto
Array
si possono fare diversamente.
Qui vedemo sono alcuni modi per utilizzare i metodi più comuni,
per moltissime ulteriori informazioni è possibile visualizzare la
pagina di Array
su MDN.
Per semplificare la trattazione definiamo subito tre variabili che utilizzeremo in tutti gli esempi:
let numeri = [1,12,7,4,3,10];
let oggetti = [
{testo:"hello",valore:4},
{testo:"map",valore:3},
{testo:"reduce",valore:8}
];
let contenitore = document.getElementById("selectNomi");
un vettore di numeri, uno di oggetti e un oggetto del DOM (un select nella pagina!).
Elaborarare tutti gli elementi di un vettore
Stampare tutti i numeri contenuti in un vettore sulla console
Iniziamo da una cosa che capita di fare spesso: voglio fare una qualche operazione su tutti gli elementi di un vettore uno per uno, ad esempio stamparli sulla console:
for(let i=0 ; i<numeri.length ; i++){
console.log(numeri[i]);
}
La parte di codice precedente fa il suo lavoro, però molti linguaggi attuali offrono la possibilità di lavorare sugli stream cioè di immaginare non un vettore di cui si guarda una casella alla volta ma un flusso di elementi che arrivano uno dietro l'altro (alla fine l'indice dove stanno nel vettore importa poco), in pratica lo stesso lavoro possiamo farlo così:
numeri.forEach( (x) => { console.log(x); });
Parafrasi: Per ogni elemento x
che sta nell'Array numeri
esegui console.log(x)
.
Forse è il caso di richiamare la sintassi di "=>", quella che viene specificata nelle parentesi tonde del forEach è una funzione che verrà chiamata per ogni elemento del vettore x passandogli il valore dell'elemento come parametro, cioè la parte nelle graffe viene eseguita una volta per il numero 1, una volta per il 12, una volta per il 7 e così via.
È una versione contratta (e magari anche più leggibile) di:
function elabora(x){
console.log(x);
}
numeri.forEach( elabora );
Creare gli option per il select
I dati li prendiamo dal vettore di oggetti chiamato oggetti
.
for(let i=0 ; i<oggetti.length ; i++){
let elemento = document.createElement("option");
elemento.innerText = oggetti[i].testo;
elemento.value = oggetti[i].valore;
contenitore.appendChild(elemento);
}
Usando il forEach()
viene così:
oggetti.forEach( e => {
let elemento = document.createElement("option");
elemento.innerText = e.testo;
elemento.value = e.valore;
contenitore.appendChild(elemento);
});
Filtri
Può capitare che dei dati che ci arrivano non ci serva di utilizzarli tutti ma soltanto una parte, parte che possiamo selezionare utilizzando opportuni criteri. Vediamo come si può procedere seguendo i due diversi approcci e lavorando al solito su numeri e oggetti
Stampare i soli numeri pari
for(let i=0 ; i<numeri.length; i++){
if(numeri[i]%2==0){
console.log(numeri[i]);
}
}
Oppure posso usare il metodo filter()
che prende come parametro una funzione
che usa appunto da filtro: gli viene passato un elemento alla volta e
se il risultato della funzione (un test solitamente) è true prende l'elemento altrimenti lo scarta
numeri.filter(n => n%2==0 ).forEach( x => { console.log(x); });
Qui sopra filter()
prende gli elementi pari che vengono poi
stampati con il forEach
.
Trasformare qualcosa in qualcos'altro
Questa strategia (parte del pattern conosciuto come map/reduce) è utile quando si vuol trasformare un qualcosa in qualcos'altro, forse più facile da capire con un paio di esempi come al solito.
Il doppio di ciascun elemento di un vettore
let risultato = [];
for(let i=0;i < numeri.length;i++){
risultato.push(numeri[i]*2);
}
Quella che segue è la versione che usa map()
.
let risultato = numeri.map( n => n*2 );
Creare degli option
Con lo stesso schema possiamo creare gli elementi option
per un select
, il vantaggio dal punto di vista della lunghezza
del codice è minore, ragiona così: da ogni elemento del vettore
oggetti
creo il corrispondente Node
(un elemento della pagina HTML)
poi un elemento alla volta lo aggiungo al select.
oggetti.map( elemento => {
let n = document.createElement("option");
n.innerText = elemento.testo;
n.value = elemento.valore;
return n;
}).forEach( x => contenitore.appendChild(x));
Creare dei riassunti
L'altra parte del pattern map/reduce prevede di creare un unico oggetto alla fine del lavoro, proviamo a vedere ad esempio come sommare un vettore:
let somma = 0;
for(let i=0 ; i<numeri.length ; i++){
somma = somma+numeri[i];
}
E la versione aggiornata:
let somma = numeri.reduce( (somma, v) => somma+v, 0);
Il metodo reduce()
prende due parametri:
- (somma, v) => somma+v
- una funzione a cui passa due valori: una variabile detta accumulatore cioè quella che serve a fare la sintesi (la somma nel nostro caso) e l'elemento dello stream, il risultato di questa funzione finisce di nuovo nell'accumulatore
- 0
- il valore da dare all'inizio all'accumulatore (0 nel nostro caso perché facciamo una somma)
Tutto insieme
Vediamo un esempio pratico completo: mi arriva un insieme di dati (un vettore!) e io voglio
creare una select utilizzandone soltanto alcuni (quelli che hanno un valore inferiore
a cinque). Questa volta però creiamo una stringa da inserire nel select con innerHTML
let stringa = "";
for(let i=0 ; i<oggetti.length ; i++){
if(oggetti[i].valore<5){
stringa+=`<option value=${oggetti[i].valore}>${oggetti[i].testo}</option>`;
}
}
contenitore.innerHTML = stringa;
contenitore.innerHTML = oggetti
.filter( elemento => elemento.valore<5 )
.map(elemento => `<option value=${elemento.valore}>${elemento.testo}</option>`)
.join();
Il join()
congiunge tutte le stringhe in una sola stringa
(un modo sbrigativo per fare un reduce).