Array in JS: un mondo di possibilità!
In JavaScript, un Array è una singola variabile che viene utilizzata per memorizzare diversi elementi. Sono spesso usate quando vogliamo memorizzare una lista di elementi e accedervi con una singola variabile. È un modo più conveniente per memorizzare e strutturare le informazioni rispetto alla definizione di molte variabili con nomi leggermente diversi.
Per essere precisi, come detto dalla pagina MDN:
Gli Array sono oggetti di tipo lista il cui prototipo ha metodi per performare operazioni trasversali e di mutazione. Nè la lunghezza di un array JavaScript o i tipi dei suoi elementi sono fissati.
Gli array JavaScript sono zero-indicizzati: il primo elemento di un array è all’inidice
0
, e l'ultimo elemento è all'indice uguale al valore della proprietà dell'arraylength
meno 1.
Ma in fondo tutte queste cose le sappiamo già!
Quello che forse non sappiamo è quanto potenti possano essere gli Array in JavaScript e quanto possano ridurre la quantità di codice da redigere (e quindi che potenzialmente possa contenere bugs che, si sa, sono sempre dietro l’angolo).
In particolar modo, dall’arrivo di ES6 e successivi il mondo degli Array si è arricchito di tantissime possibilità e molte di queste si riflettono sul loro utilizzo in maniera piuttosto importante.
Questo articolo desidera essere una sorta di comodo decalogo (o Cookbook?) di qualche comune caso d’uso che possiamo incontrare e quindi affrontare in maniera intelligente!
I metodi e le proprietà più comuni
Facciamo un breve ripasso di tutti i metodi e le proprietà più comuni degli Array, così da poter affrontare i vari use cases in maniera un pizzico più consapevole.
Generali
- Dichiarazione di un array:
// Comunemente lo si dichiara con l'utilizzo delle parentesi quadre
let arrA = [item1, item2, item3, ...];
// oppure utilizzando il costruttore (usato molto raramente)
let arrB = new Array(item1, item2, item3, ...);
length
– Indica la dimensione dell’Array
(precisamente l’ultimo indice presente + 1);Array.isArray(arr)
– Verifica se il valore passato è unArray
(dato chetypeof
tornerebbe come risultatoobject
);Array.from(obj[, mapFn, thisArg])
– Crea un’istanzaArray
da un Iterable o un array-like object;Array.of()
– Crea un’istanzaArray
da un numero variadico di parametri passati.
Modifiche
push(item1, item2, ...)
– Aggiungeitems
alla fine dell’Array
e ritorna il nuovo valore dilength
;pop()
– Rimuove e ritorna l’ultimo item alla fine dell’Array
;unshift(item1, item2, ...)
– Aggiungeitems
all’inizio dell’Array
e ritorna il nuovo valore dilength
;shift()
– Rimuove e ritorna l’ultimo item all’inizio dell’Array
;splice(p, n, item1, item2, ...)
– Consente con un unico metodo di rimuoveren
items a partire dalla posizionep
e contemporaneamente aggiungereitems
nella stessa posizionep
. Come valore di ritorno ha unArray
con i valori eliminati. Una sorta di coltellino svizzero!slice(start, end)
– Crea, come valore di ritorno, un nuovoArray
con gli elementi dell’array d’origine inclusi dalla posizionestart
sino alla posizioneend
(non inclusa);concat(item1, item2, ...)
– Crea, come valore di ritorno, un nuovoArray
composto da tutti gli items dell’Array
d’origine e gliitems
passati come parametri (che possono essere ancheArray
dei quali saranno accodati gli items);arr.fill(value, start, end)
– Popola l’Array
ripetendovalue
dalla posizionestart
allaend
;arr.copyWithin(target, start, end)
– Ricopia nella posizionetarget
gli items dalla posizionestart
allaend
(sovrascrivendo).
Ricerca
indexOf(item, pos)
– Ritorna l’indice in cui viene trovatoitem
partendo dalla posizionepos
. Nel caso non venga trovato nessun item, sarà ritornato il valore -1. È anche presente il metodolastIndexOf
che ritorna l’ultimo indice trovato;includes(item)
– Ritornatrue
se l’array contieneitem
, altrimenti viene tornato il valorefalse;
filter(callback)
– Crea, come valore di ritorno, un nuovoArray
con gli items filtrati secondo la funzionecallback
passata come parametro (il cui risultato deve esseretrue
);find(callback)
– Ritorna il valore del primo item trovato che soddisfa la condizione della funzionecallback
passata come parametro (il cui risultato deve esseretrue)
;findIndex(callback)
– Ritorna l’indice del primo item che soddisfa la condizione della funzionecallback
passata come parametro (il cui risultato deve esseretrue)
;some(callback)
– Ritornatrue
se l’Array
contiene almeno un item che soddisfa la condizione della funzionecallback
passata come parametro (il cui risultato deve esseretrue)
;every(callback)
– Ritornatrue
se l’Array
contiene tutti gli items che soddisfano la condizione della funzionecallback
passata come parametro (il cui risultato deve esseretrue)
.
Iterazione
forEach(callback(item, index, array)
– Esegue la funzionecallback
per ogni items dell’Array
. Il metodo non ritorna nulla ma nella funzionecallback
è possibile utilizzare, oltre all’item
corrente anche l’indiceindex
e l’Array
che stiamo ciclando.
Trasformazione
map(callback)
– Crea, come valore di ritorno, un nuovoArray
con i risultati della funzionecallback
eseguita per ogni items dell’Array
d’origine;sort(callback)
– Ordina l’Array
basandosi sulla conversione degli items in stringhe (e quindi in maniera lessicografica).
Nel caso venga passata come parametro una funzionecallback
sarà possibile definire un ordinamento custom;reverse()
– Inverte l’ordine degli items dell’Array
;reduce(callback, initial)
– Ritorna un singolo valore dall’iterazione dei singoli items dell’Array
, passando per singola iterazione alla funzionecallback
il valore intermedio tra le chiamate. Il valore intermedio sarà quindi utilizzato per accumulare il valore di ritorno;flat(depth)
– Utile per appiattire gliArray
nidificati. Crea, come valore di ritorno, un nuovoArray
con tutti gli items dell’Array
d’origine, valutando il livello di nidificazione definito col parametrodepth
(che ha valore di default 1). È inoltre presente il metodoflatMap
che consente di applicare un mapping degli items in fase di flattening (ed il valore di profondità è sempre pari ad 1).join(glue)
– Crea una stringa, come valore di ritorno, composta da tutti gli elementi dell’array concatenati dal valore del separatoreglue
.
Operazioni comuni
Spesso bisogna compiere operazioni comuni con i nostri Array ed esistono delle soluzioni rapide ed efficaci per farle, sfruttando molti dei metodi e delle proprietà che abbiamo appena visto.
Creare un Array da Elementi o Iterabili
// Da Elementi
const arrA = Array.of(1, 2, 3, 4, 5, 6);
console.log(arrA); // output: [1, 2, 3, 4, 5, 6]// Da Array o Iterabili
const arrB = Array.from(document.querySelectorAll('li'));
Svuotare un Array
// Utilizzando il metodo splice
let arrA = ['Francesco', 'Andrea', 'Salvo'];
arrA.splice(0, arrA.length);// Settando il valore della proprietà length a 0
let arrB = ['Francesco', 'Andrea', 'Salvo'];
arrB.length = 0
Aggiungere/Rimuovere Elementi (Queue/FIFO Mode)
let arr = [1, 2, 3, 4, 5];arr.shift() // output: [2, 3, 4, 5];
arr.push(6); // output: [2, 3, 4, 5, 6]
Aggiungere/Rimuovere Elementi (Stack/LIFO Mode)
let arr = [1, 2, 3, 4, 5];arr.push(6); // output: [1, 2, 3, 4, 5, 6]
arr.pop(); // output: [1, 2, 3, 4, 5]
Inversione della posizione degli items
let arr = ["fs", 2, "50", 3];
arr.reverse() // output: [3, "50", 2, "fs"]
Ordinamento Lessicografico
let arr = ["fs", 2, "50", 3];
arr.sort(); // output: [2, 3, "50", "fs"]
Ordinamento Custom
let arr = [10, 4, 5, 74, 11];
arr.sort((el1, el2) => !(el1 - el2)); // output: [4, 5, 10, 11, 74]
Lavorare con gli insiemi
Come suggerisce la cara Wikipedia:
Un raggruppamento di oggetti rappresenta un insieme se esiste un criterio oggettivo che permette di decidere univocamente se un qualunque oggetto fa parte o no del raggruppamento.
Le operazioni tra gli insiemi sono comunemente non semplici da realizzare, e JavaScript per tanti anni non ci ha offerto soluzioni molto facili a riguardo.
Oggi invece, con l’aiuto di filter
, concat
, le arrow functions, lo spread operator e del built-in object Set possiamo eseguire le operazioni in una singola riga!
Intersezione (Items comuni tra gli array)
let arrA = [1,2,3];
let arrB = [2,5,3];let intersectionArr = arrA.filter(value => arrB.includes(value));
console.log(intersectionArr); // output: [2,3]
Differenza (Items presenti solo sull’array filtrato)
let arrA = [1,2,3];
let arrB = [2,5,3];let differenceArr = arrA.filter(value => !arrB.includes(value));
console.log(differenceArr); // output: [1]
Differenza Simmetrica (Items non comuni tra gli array)
let arrA = [1,2,3];
let arrB = [2,5,3];let differenceArr = arrA
.filter(value => !arrB.includes(value))
.concat(arrB.filter(value => !arrA.includes(value)));
console.log(differenceArr); // output: [1,5]
Unione (Tutti gli items dei due array con eventuali duplicati)
let arrA = [1,2,3];
let arrB = [2,5,3];let unionArr = [...arrA, ...arrB];
console.log(unionArr); // output: [1,2,3,2,5,3]
Unione (Tutti gli items dei due array senza duplicati)
let arrA = [1,2,3];
let arrB = [2,5,3];let unionArr = [...new Set([...arrA, ...arrB)];
console.log(unionArr); // output: [1,2,3,5]
Iterazione
L’iterazione di un array è una delle operazioni più comuni ed esistono svariati tipi di costrutti che consentono di farlo.
Sicuramente tra le più utilizzate troviamo (in ordine di frequenza):
for (let item of arr)
– Consente di ottenere il valoreitem
per ogni iterazione dell’arrayarr
;for (let i = 0; i < arr.length; i++)
– Il ciclo for canonico per eccellenza!for (let key in arr)
– Consente di ottenere la chiavekey
per ogni iterazione dell’arrayarr
. Questo costrutto non è quasi mai utilizzato.
Mutare gli Array
Inserire, rimuovere e modificare item degli array è qualcosa di molto comune ed è estremamente facile farlo!
Creare un Array popolato con valori statici
// Con il metodo from (passando un array-like object)
let arrB = Array.from({length:10},()=> 10);// Con lo spread operator, il costruttore Array ed il metodo map
let arrC = [...new Array(10)].map(()=> 10);// Con il costruttore Array ed metodo fill
// (da evitare preferendo le altre forme su indicate)
let arrA = new Array(10).fill(10);
Rimuovere Items da posizioni specifiche
let arr = ["Ciao", "da", "Francesco", "Sciuti"];
arr.splice(1, 1);
console.log(arr); // output: ["Ciao", "Francesco", "Sciuti"];
Sostituire Items in posizioni specifiche
let arr = ["Ciao", "da", "Francesco", "Sciuti"];arr.splice(0, 2, "io", "sono");
console.log(arr) // output: ["io", "sono", "Francesco", "Sciuti"];
Inserire Items in posizioni specifiche
let arr = ["io", "sono", "Francesco", "Sciuti"];
arr.splice(2, 0, "il", "sig.");
console.log(arr); // output: ["io", "sono", "il", "sig.", "Francesco", "Sciuti"];
Immutabilità
L’immutabilità è un principio fondamentale nella programmazione funzionale.
Sappiamo però che in JavaScript in fase di assegnazione le variabili di tipo Array (ed anche gli Oggetti) vengono passate per riferimento e quindi quello che può accadere è che più variabili possano puntare alla stessa struttura dati!
Di seguito, effettueremo alcune operazioni che ci aiuteranno invece a mantenere immutabili gli array.
Clonare un Array
const sourceArray = [1,2,3];// Via spread operator
const clonedArray = [...sourceArray];// Via metodo slice
const clonedArray = sourceArray.slice();
Aggiungere/Rimuovere Items (Stack o Queue)
function immutablePush(arr, newEntry){
return [ ...arr, newEntry ]
}function immutablePop(arr){
return arr.slice(0, -1)
}function immutableShift(arr){
return arr.slice(1)
}function immutableUnshift(arr, newEntry){
return [ newEntry, ...arr ]
}
Ordinamento e Inversione della posizione degli Items
function immutableSort(arr, compareFunction) {
return [ ...arr ].sort(compareFunction)
}function immutableReverse(arr) {
return [ ...arr ].reverse()
}
CRUD — Aggiungere un Item
const team = [
{ id: 1, name: "Ciccio", age: 42, skill: "frontend" },
{ id: 2, name: "Andrea", age: 36, skill: "backend" },
{ id: 3, name: "Salvo", age: 36, skill: "devops" }
];const member = { id: 4, name: "Peppe", age: 26, skill: "frontend" };
let newTeam = [...team, member];
CRUD — Eliminazione di un Item
const team = [
{ id: 1, name: "Ciccio", age: 42, skill: "frontend" },
{ id: 2, name: "Andrea", age: 36, skill: "backend" },
{ id: 3, name: "Salvo", age: 36, skill: "devops" }
];// Eliminazione via proprietà
const id = 2;
newTeam = team.filter(m => m.id !== id)// Eliminazione via indice della posizione
const index = 1;
newTeam = [...team.slice(0, index), ...team(index + 1)];
CRUD — Modifica/Sostituzione di un Item
const team = [
{ id: 1, name: "Ciccio", age: 42, skill: "frontend" },
{ id: 2, name: "Andrea", age: 36, skill: "backend" },
{ id: 3, name: "Salvo", age: 36, skill: "devops" }
];// Modifica di un Item (via map method e spread operator)
const updatedMember = { id: 1, age: 41 };
newTeam = team.map(m => m.id === updatedMember.id ? { ...m, ...updatedMember } : m);// Sostituzione di un Item
const memberToReplace = { id: 3, name: "Nico", age: 30, skill: "frontend" };// Non valutando la posizione precedente
const newTeam = [...team.filter(member => member.id !== memberToReplace.id), memberToReplace];// Valutando la posizione precedente
const indexOldItem = team.findIndex(({ id }) => id == memberToReplace.id);
const newArray = [...team.slice(0, indexOldItem), memberToReplace, ...team.slice(indexOldItem + 1)]
Tips & Tricks
Alcuni casi particolari che possono sembrare complessi ma che possiamo risolvere con pochissimo sforzo.
Eseguire una funzione per ogni Item
const arr = [3, 1, 3, 5];
const mappedArr = arr.map((item) => i * 2);
console.log(mappedArr);// output: [6, 2, 6, 10]
Rimuovere i duplicati
const arr = [3, 1, 3, 5, 2, 4, 4, 4];
const uniqueValuesArr = [...new Set(arr)];
console.log(uniqueValuesArr);// output: [3, 1, 5, 2, 4]
Ricerca Case-Sensitive
const team = [
{ id: 1, name: "Ciccio", age: 42, skill: "frontend" },
{ id: 2, name: "Andrea", age: 36, skill: "backend" },
{ id: 3, name: "Salvo", age: 36, skill: "devops" }
];let res = team.filter(member => member.skill.includes('end'));
console.log(res);// output: [
{ id: 1, name: "Ciccio", age: 42, skill: "frontend" },
{ id: 2, name: "Andrea", age: 36, skill: "backend" }
];
Ricerca Case-Insensitive
const team = [
{ id: 1, name: "Ciccio", age: 42, skill: "frontend" },
{ id: 2, name: "Andrea", age: 36, skill: "backend" },
{ id: 3, name: "Salvo", age: 36, skill: "devops" }
];let res = team.filter(member => new RegExp('Cic', "i").test(member.name));
console.log(res);// output: [{ id: 1, name: "Ciccio", age: 42, skill: "frontend" }];
Verifica se almeno un item supera una condizione
const team = [
{ id: 1, name: "Ciccio", age: 42, skill: "frontend" },
{ id: 2, name: "Andrea", age: 36, skill: "backend" },
{ id: 3, name: "Salvo", age: 36, skill: "devops" }
];const hasFrontend = team.some(member => member.skill === 'frontend');
console.log(hasFrontend);// output: true
Verifica se tutti gli elementi superano una condizione
const team = [
{ id: 1, name: "Ciccio", age: 42, skill: "frontend" },
{ id: 2, name: "Andrea", age: 36, skill: "backend" },
{ id: 3, name: "Salvo", age: 36, skill: "devops" }
];
// all elements are greater than 4
const memberGreaterTirthy = team.every(num => num > 30);
console.log(memberGreaterTirthy);// output: false
Flattening di Array
//Flattening con metodo Reduce
const nestedArr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
let flatArr = nestedArr.reduce((acc, it) => [...acc, ...it], []);
console.log(flatArr);// output: [1, 2, 3, 4, 5, 6, 7, 8, 9]//Flattening con metodo Flat
let flatMethodArr = nestedArr.flat();
console.log(flatMethodArr);// output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Cardinalità degli Items
const team = [
{ id: 1, name: "Ciccio", age: 42, skill: "frontend" },
{ id: 2, name: "Andrea", age: 36, skill: "backend" },
{ id: 3, name: "Salvo", age: 36, skill: "devops" }
];const teamByAge = team.reduce((acc, item) => {
acc[item.age] = acc[item.age] + 1 || 1;
return acc;
}, {});
console.log(teamByAge);// output: {42: 1, 36: 2}
Estrarre i valori unici di un array di una specifica chiave degli items
const team = [
{ id: 1, name: "Ciccio", age: 42, skill: "frontend" },
{ id: 2, name: "Andrea", age: 36, skill: "backend" },
{ id: 3, name: "Salvo", age: 36, skill: "devops" }
];const listOfSkills = [...new Set(team.map(item => item.skill))];
console.log(listOfSkills);// output: ['frontend', 'backend', 'devops'];
Detect di un Array
let arrA = ['Francesco', 'Andrea', 'Salvo'];console.log(Array.isArray(arrA)); // output: true
Conclusioni
Mi auguro che questo piccolo decalogo (o cookbook?) possa aiutarvi nei momenti difficili in cui vorreste lanciare il computer in aria per colpa degli Array o quando dovrete semplicemente affrontare uno dei casi elencati.
Per suggerimenti (o aggiunte perché no!), critiche, insulti, lodi, donazioni di enormi quantità di denaro, partite a padel o consigli non serve altro che scrivere nei commenti!
Un ringraziamento particolare a Salvo Pappalardo e Andrea Costa per la revisione dell’articolo. :)