Non ne potrai fare a meno
Nella vita di sviluppatore puoi aver avuto a che fare con loro (o meglio DOVRESTI), la Santa Trinità dei metodi magici sugli array: ma se non hai mai approfondito ti consiglio di leggere e soprattutto provare (anche utilizzando la semplice console del browser - F12) queste funzioni…
Funzioni che ricordo oramai sono standard nell’ecosistema JS (ES5, anno domini 2009) ma che sono presenti anche nella maggioranza degli altri linguaggi di programmazione più prettamente backend (Python, PHP, etc…)
Funzioni che ti consentiranno di manipolare gli array scrivendo meno codice, e soprattutto scrivendo codice testabile, scalabile e manutenibile.
Funzioni di ordine superiore… ma ne parleremo dopo!
Esempi con array di valori numerici ne trovate a bizzeffe in rete 🤘… Proviamo ad utilizzare, invece, un array di oggetti che rappresentano figure professionali con i loro ‘oggetti’ del mestiere: vediamo che ne esce fuori!
const data = [
{ object: '🧰', worker: '👨🔧', expertise: 1 },
{ object: '✈️', worker: '👨✈️', expertise: 7 },
{ object: '🍳', worker: '👩🍳', expertise: 10 }
];
MAP
Supponiamo di voler ottenere, da tutte le figure professionali, l’azione che eseguono utilizzando i loro strumenti del mestiere (object) e la loro figura professionale (worker). Per fare ciò, analiticamente, dovremmo ciclare per ogni oggetto dell’array e in base al tipo di object (o worker, o entrambi e questa è una decisione logica dipendente dal dominio e dall’ambito di utilizzo) scegliere l’azione che può essere performata.
Ci viene in aiuto map().
Semplicemente map accetta una funzione come argomento che viene applicata ad ogni elemento dell’array chiamante: il risultato sarà aggiunto all’array ritornato dal metodo di map stesso.
E’ più facile fare che spiegare! 🤣
const startWorking = (obj) => { //implementazione SOLO DI ESEMPIO...
if(obj.object === "🧰") {
return '🚙';
}
if(obj.object === "✈️") {
return '🛫';
}
if(obj.object === "🍳") {
return '🍲';
}
};
let resultMap = data.map(startWorking);
// resultMap = ["🚙", "🛫", "🍲"]
REDUCE
In questo caso vogliamo ottenere tutti gli anni di esperienza delle nostre figure professionali. Ambito easy: contatore somma, ciclo per ogni elemento dell’array e incremento il contatore. Perchè invece non usare reduce()?
Reduce() applica una funzione reducer su ogni elemento dell’array: fornendo un valore iniziale l’accumulator (primo argomento del reducer) prende questo valore, altrimenti viene preso il primo valore dell’array. Il valore restituito dal reducer viene assegnato e memorizzato nell’accumulatore attraverso ogni iterazione dell’intero array: tale valore sarà poi il valore finale della funzione.
NB: Se non viene fornito initialValue, il reducer partirà dall’indice 1 dell’array, se invece viene fornito, inizierà dall’indice 0.
const yearExpertise = (accumulator, work) => accumulator + work.expertise;
let sum = data.reduce(yearExpertise, 0);
// sum = 18
let sum2 = data.reduce(yearExpertise, 5);
// sum = 23
FILTER
Vogliamo ora ottenere, tra tutte le figure professionali, quelle che hanno a che fare con la cucina. Ci viene in aiuto il buon filter().
Filter() applica una funzione di filtro a tutti gli elementi dell’array: solo quelli che passano il test implementato nella funzione di filtro saranno presenti nell’array risultante.
const inKitchen = (work) => { //implementazione SOLO DI ESEMPIO...
return ["🍳","🔪", "🍴"].indexOf(work.object) > -1 ? true : false;
}
let worksInKitchen = data.filter(inKitchen);
// worksInKitchen = [ { object: '🍳', worker: '👩🍳', expertise: 10 } ];
COMBINIAMO I METODI
Proprio per il fatto che tutti i metodi presenti sono chiamati su array e che map e filter ritornano array, possiamo combinare i metodi per poter creare delle manipolazioni di dato pulite, chiare e facilmente testabili.
Vogliamo, per esempio, ottenere gli anni di esperienza totali di tutte le expertise che non riguardano la cucina.
- Per prima cosa filtriamo i dati cercando expertise NON di cucina
- Mappiamo l’array risultante dal punto 1. per creare un array di anni-esperienza (array di number)
- Usiamo reduce() per ottenere gli anni totali di esperienza
PS: naturalmente i punti 2. e 3. potrebbero essere implementati semplicemente con il reduce() sull’array del punto 1.
const notInKitchen = (work) => { //implementazione SOLO DI ESEMPIO...
return ["🍳","🔪", "🍴"].indexOf(work.object) > -1 ? false : true;
}
const yearExpertise = (accumulator, expertise) => accumulator + expertise;
let expertiseNotKitchen = data.filter(notInKitchen)
.map((work) => work.expertise)
.reduce(yearExpertise, 0);
// expertiseNotKitchen = 8;
Cosa mi piace di questi 3 metodi? Semplice. Lo dico in tre parole: funzioni di ordine superiore.
map(), filter(), reduce() sono tutte e tre funzioni di ordine superiore. Ciò significa che sono funzioni che accettano funzioni come parametro (o che ritornano funzioni come risultato, ma non è questo il caso).
Tutto ciò si traduce nella possibilità di migrare del codice, scritto in vecchio stile procedurale, in stile funzionale. Tutto il flusso di esecuzione è composto dall’esecuzione di funzioni, funzioni che prendon in input dati e ritornano dati (e o funzioni a loro volta).
Potendo far questo il codice che ne risulta, come nell’esempio sopra, è più leggibile, maggiormente parlante (senza bisogno di documentazione… il codice ben scritto è parlante da se) e testabile: testando unitariamente le funzioni di callback (esempio notInKitchen o yearExpertise), che sono funzioni pure, abbiamo codice robusto e scalabile senza intaccare l’integrità del risultato finale.