`Array` oggetto

Questo articolo spiega l'oggetto Array.

Spiegherò gli usi pratici degli array passo dopo passo in modo semplice da capire.

YouTube Video

Array oggetto

L'oggetto Array di JavaScript è una delle strutture più importanti che costituiscono la base di ogni tipo di elaborazione dati. Dalle operazioni di base sugli array fino alle funzioni di ordine superiore utili per trasformazioni efficienti dei dati, ci sono molte funzionalità che dovresti conoscere.

Basi dell'Array

In JavaScript, gli array sono una struttura dati fondamentale per gestire più valori insieme. Qui presentiamo come creare array e come leggere e scrivere i loro elementi con esempi semplici.

 1// Create arrays in different ways
 2const arr1 = [1, 2, 3];           // array literal
 3const arr2 = new Array(4, 5, 6);  // Array constructor
 4const arr3 = Array.of(7, 8, 9);   // Array.of
 5
 6console.log("arr1 created with literal.   :", arr1);
 7console.log("arr2 created with constructor:", arr2);
 8console.log("arr3 created with Array.of.  :", arr3);
 9// arr1 created with literal.   : [ 1, 2, 3 ]
10// arr2 created with constructor: [ 4, 5, 6 ]
11// arr3 created with Array.of.  : [ 7, 8, 9 ]
12
13// Access and modify elements
14let first = arr1[0];              // read element
15console.log("First element of arr1:", first);
16// First element of arr1: 1
17
18arr1[1] = 20;                     // modify element
19console.log("arr1 after modifying index 1:", arr1);
20// arr1 after modifying index 1: [ 1, 20, 3 ]
21
22const len = arr1.length;          // get length
23console.log("Length of arr1:", len);
24// Length of arr1: 3
  • Questo codice mostra tre modi per creare array, come leggere e aggiornare gli elementi usando gli indici, e come ottenere la lunghezza usando la proprietà length.
  • I letterali degli array sono i più comuni e leggibili, e vengono usati più frequentemente nelle situazioni quotidiane.

Aggiunta e rimozione di elementi (alla fine o all'inizio)

Gli array ti permettono di aggiungere o rimuovere facilmente elementi alla fine o all'inizio. Queste operazioni sono anche utili nell'implementare strutture come stack o code.

 1// Push and pop (stack-like)
 2const stack = [];
 3console.log("Initial stack:", stack);
 4
 5stack.push(1);                    // push 1
 6console.log("After push(1):", stack);
 7
 8stack.push(2);                    // push 2
 9console.log("After push(2):", stack);
10
11const last = stack.pop();         // pop -> 2
12console.log("Popped value:", last);
13console.log("Stack after pop():", stack);
14
15// Unshift and shift (queue-like)
16const queue = [];
17console.log("Initial queue:", queue);
18
19queue.push('a');                  // add to end
20console.log("After push('a'):", queue);
21
22queue.unshift('start');           // add to front
23console.log("After unshift('start'):", queue);
24
25const firstItem = queue.shift();  // remove from front
26console.log("Shifted value:", firstItem);
27console.log("Queue after shift():", queue);
28
29// Initial stack: []
30// After push(1): [ 1 ]
31// After push(2): [ 1, 2 ]
32// Popped value: 2
33// Stack after pop(): [ 1 ]
34
35// Initial queue: []
36// After push('a'): [ 'a' ]
37// After unshift('start'): [ 'start', 'a' ]
38// Shifted value: start
39// Queue after shift(): [ 'a' ]
  • push e pop operano sulla fine di un array. Sono ideali per implementare strutture di tipo stack.
  • unshift e shift operano sull’inizio di un array. Tuttavia, tieni presente che operare sull'inizio richiede uno spostamento interno di tutti gli indici degli elementi, rendendo l'operazione costosa.

Gestione degli elementi al centro (splice e slice)

Quando gestisci elementi al centro di un array, scegli tra splice e slice a seconda che tu voglia o meno modificare l’array originale. Se vuoi solo estrarre una parte di un array, usa slice; se vuoi modificare l'array stesso, ad esempio inserendo o eliminando elementi, usa splice.

 1// slice (non-destructive)
 2const nums = [0, 1, 2, 3, 4];
 3console.log("Original nums:", nums);
 4
 5const part = nums.slice(1, 4); // returns [1, 2, 3]
 6console.log("Result of nums.slice(1, 4):", part);
 7console.log("nums after slice (unchanged):", nums);
 8
 9// splice (destructive)
10const arr = [10, 20, 30, 40];
11console.log("\nOriginal arr:", arr);
12
13// remove 1 item at index 2, insert 25 and 27
14arr.splice(2, 1, 25, 27);
15console.log("After arr.splice(2, 1, 25, 27):", arr);
16
17// Original nums: [ 0, 1, 2, 3, 4 ]
18// Result of nums.slice(1, 4): [ 1, 2, 3 ]
19// nums after slice (unchanged): [ 0, 1, 2, 3, 4 ]
20
21// Original arr: [ 10, 20, 30, 40 ]
22// After arr.splice(2, 1, 25, 27): [ 10, 20, 25, 27, 40 ]
  • slice estrae solo gli elementi e non modifica l’array originale.
  • splice aggiunge o rimuove elementi e modifica l’array stesso, quindi fai particolare attenzione al suo impatto sul comportamento.

Iterazione (for / for...of / forEach)

Ci sono diversi modi per elaborare gli array in sequenza e puoi scegliere secondo lo scopo e le tue preferenze di stile di codifica. Ecco tre costrutti tipici di ciclo.

 1const items = ['apple', 'banana', 'cherry'];
 2console.log("Items:", items);
 3
 4// classic for
 5console.log("\n--- Classic for loop ---");
 6for (let i = 0; i < items.length; i++) {
 7  console.log(`Index: ${i}, Value: ${items[i]}`);
 8}
 9
10// for...of (recommended for values)
11console.log("\n--- for...of loop ---");
12for (const item of items) {
13  console.log(`Value: ${item}`);
14}
15
16// forEach (functional style)
17console.log("\n--- forEach loop ---");
18items.forEach((item, index) => {
19  console.log(`Index: ${index}, Value: ${item}`);
20});
  • Il ciclo for è il più flessibile, permettendo operazioni sugli indici e un controllo preciso dell’iterazione usando break e altre istruzioni.
  • for...of offre un modo conciso per gestire i valori degli elementi ed è il più bilanciato in termini di leggibilità.
  • forEach consente un codice in stile funzionale ed è adatto a operazioni con effetti collaterali, come il logging o l'aggiornamento dei dati per ogni elemento. Tuttavia, nota che non puoi usare break o continue, e non è adatto per elaborazioni asincrone con await.

map / filter / reduce — Funzioni di ordine superiore

map, filter e reduce sono funzioni di ordine superiore frequentemente utilizzate quando si trasformano, filtrano o aggregano gli array. Poiché puoi esprimere chiaramente i processi ripetitivi, il tuo codice diventa semplice e facile da comprendere.

 1const numbers = [1, 2, 3, 4, 5];
 2console.log("Original numbers:", numbers);
 3
 4// map: transform each item
 5const doubled = numbers.map(n => n * 2);
 6console.log("\nResult of map (n * 2):", doubled);
 7
 8// filter: select items
 9const evens = numbers.filter(n => n % 2 === 0);
10console.log("Result of filter (even numbers):", evens);
11
12// reduce: accumulate to single value
13const sum = numbers.reduce((acc, n) => acc + n, 0);
14console.log("Result of reduce (sum):", sum);
15
16// Original numbers: [ 1, 2, 3, 4, 5 ]
17// Result of map (n * 2): [ 2, 4, 6, 8, 10 ]
18// Result of filter (even numbers): [ 2, 4 ]
19// Result of reduce (sum): 15
  • Questi metodi ti permettono di concentrarti su ciò che vuoi fare in uno stile dichiarativo, migliorando la leggibilità ed evitando effetti collaterali indesiderati.

find / findIndex / some / every

Di seguito una panoramica dei metodi di ricerca e verifica delle condizioni. Questi sono utili per trovare elementi che soddisfano determinate condizioni o per eseguire verifiche booleane sull’insieme.

 1const users = [
 2  { id: 1, name: 'Alice' },
 3  { id: 2, name: 'Bob' },
 4  { id: 3, name: 'Carol' }
 5];
 6
 7console.log("Users:", users);
 8
 9// Find the first user whose name is 'Bob'
10const bob = users.find(user => user.name === 'Bob');
11console.log("\nResult of find (name === 'Bob'):", bob);
12
13// Find index of the user whose id is 3
14const indexOfId3 = users.findIndex(user => user.id === 3);
15console.log("Result of findIndex (id === 3):", indexOfId3);
16
17// Check if there exists a user with id = 2
18const hasId2 = users.some(user => user.id === 2);
19console.log("Result of some (id === 2):", hasId2);
20
21// Check if all users have a numeric id
22const allHaveNumericId = users.every(user => typeof user.id === 'number');
23console.log("Result of every (id is number):", allHaveNumericId);
24// Result of find (name === 'Bob'): { id: 2, name: 'Bob' }
25// Result of findIndex (id === 3): 2
26// Result of some (id === 2): true
27// Result of every (id is number): true
  • find restituisce il primo elemento che soddisfa la condizione.
  • findIndex restituisce l’indice dell’elemento che soddisfa la condizione.
  • some restituisce true se c’è almeno un elemento che soddisfa la condizione.
  • every restituisce true se tutti gli elementi soddisfano la condizione.

Tutti questi sono molto utili nell’elaborazione degli array, quindi usarli in modo appropriato per la situazione manterrà il tuo codice conciso e chiaro.

Ordinamento e funzioni di confronto

Gli array vengono ordinati usando sort, ma per impostazione predefinita confronta gli elementi come stringhe, il che può portare a risultati inattesi quando si ordinano numeri.

 1const nums = [10, 2, 33, 4];
 2console.log("Original nums:", nums);
 3
 4// Default sort: compares elements as strings (not suitable for numbers)
 5nums.sort();
 6console.log("\nAfter default sort (string comparison):", nums);
 7
 8// Numeric ascending sort using a compare function
 9nums.sort((a, b) => a - b);
10console.log("After numeric sort (a - b):", nums);
11
12// Sort objects by a property
13const people = [{ age: 30 }, { age: 20 }, { age: 25 }];
14console.log("\nOriginal people:", people);
15
16people.sort((a, b) => a.age - b.age);
17console.log("After sorting people by age:", people);
18
19// After default sort (string comparison): [ 10, 2, 33, 4 ]
20// After numeric sort (a - b): [ 2, 4, 10, 33 ]
21// Original people: [ { age: 30 }, { age: 20 }, { age: 25 } ]
22// After sorting people by age: [ { age: 20 }, { age: 25 }, { age: 30 } ]
  • Quando ordini numeri o oggetti, specifica sempre una funzione di confronto per ordinarli nell’ordine desiderato.
  • In una funzione di confronto, un valore di ritorno negativo posiziona a prima di b, uno positivo mette b prima di a, e 0 mantiene l’ordine invariato.

Copia di array e immutabilità

Quando si copiano gli array, è importante comprendere la differenza tra 'copia per riferimento' e 'copia superficiale'. In particolare, ricorda che se ci sono oggetti all'interno dell’array, una copia superficiale farà sì che questi oggetti vengano condivisi.

Copia per riferimento

Quando assegni un array a un'altra variabile, l'array stesso non viene duplicato; invece viene copiata la 'riferimento' che punta allo stesso array.

 1const a = [1, 2, 3];
 2console.log("Original a:", a);
 3
 4// Reference copy; modifying b also affects a
 5const b = a;
 6console.log("\nReference copy b = a:", b);
 7// Reference copy b = a: [ 1, 2, 3 ]
 8
 9b[0] = 100;
10console.log("After modifying b[0] = 100:");
11console.log("a:", a);  // a: [ 100, 2, 3 ] (affected)
12console.log("b:", b);  // b: [ 100, 2, 3 ]
  • Con una copia per riferimento, se modifichi il contenuto dell'array utilizzando la variabile copiata, tali modifiche saranno riflesse anche nella variabile originale che si riferisce allo stesso array.

Copia superficiale

Utilizzando slice() o la sintassi spread viene creata una 'copia superficiale' perché solo i valori vengono duplicati; gli array originale e copiato sono trattati come entità separate.

 1const a = [1, 2, 3];
 2console.log("Original a:", a);
 3
 4// Shallow copy (new array)
 5const b = a.slice();
 6console.log("\nShallow copy using slice():", b);
 7// Shallow copy using slice(): [ 100, 2, 3 ]
 8
 9const c = [...a];
10console.log("Shallow copy using spread [...a]:", c);
11// Shallow copy using spread [...a]: [ 100, 2, 3 ]
12
13// Modifying c or d does NOT affect a
14b[1] = 200;
15c[2] = 300;
16console.log("\nAfter modifying b[1] = 200 and c[2] = 300:");
17console.log("a:", a);   // [ 100, 2, 3 ]
18console.log("b:", b);   // [ 100, 200, 3 ]
19console.log("c:", c);   // [ 100, 2, 300 ]
  • Questo codice dimostra che una copia superficiale di un array creata tramite slice() o la sintassi spread non influisce sull'array originale.

Copia superficiale e immutabilità

Anche se duplichi un array utilizzando una 'copia superficiale', può verificarsi una condivisione involontaria se l'array contiene oggetti al suo interno.

 1// Shallow copy doesn't clone inner objects
 2const nested = [{ x: 1 }, { x: 2 }];
 3console.log("\nOriginal nested:", nested);
 4// Original nested: [ { x: 1 }, { x: 2 } ]
 5
 6const shallow = nested.slice();
 7console.log("Shallow copy of nested:", shallow);
 8// Shallow copy of nested: [ { x: 1 }, { x: 2 } ]
 9
10// Changing inner object affects both arrays
11shallow[0].x = 99;
12console.log("\nAfter shallow[0].x = 99:");
13console.log("nested:", nested);
14console.log("shallow:", shallow);
15// nested: [ { x: 99 }, { x: 2 } ]
16// shallow: [ { x: 99 }, { x: 2 } ]
  • Questo codice dimostra che con una copia superficiale, gli oggetti interni sono condivisi, quindi modificando tali oggetti si influenzano sia l'array originale sia la copia.
  • Se hai bisogno di dati indipendenti, hai bisogno di una 'copia profonda', ad esempio usando structuredClone() o la conversione tramite JSON.

Metodi di utilità utili

I seguenti sono metodi di utilità frequentemente usati quando si lavora con gli array. Utilizzandoli in modo appropriato per il tuo scopo, puoi scrivere codice corto e leggibile.

includes

Il metodo includes verifica se un valore specifico è incluso in un array.

1// includes: check if array contains a value
2const letters = ['a', 'b', 'c'];
3const hasB = letters.includes('b');
4console.log("letters:", letters);  // [ 'a', 'b', 'c' ]
5console.log("letters.includes('b'):", hasB); // true
  • In questo codice, il metodo includes viene utilizzato per determinare in modo conciso se un valore specificato esiste nell'array.

concat

Il metodo concat restituisce un nuovo array aggiungendo l'array o i valori specificati alla fine, mantenendo invariato l'array originale.

1// concat: merge arrays without mutation
2const a1 = [1, 2];
3const a2 = [3, 4];
4const combined = a1.concat(a2);
5console.log("a1.concat(a2):", combined); // [ 1, 2, 3, 4 ]
6console.log("a1(unchanged):", a1);       // [ 1, 2 ]
  • Questo codice mostra che concat è non distruttivo e ti permette di generare un nuovo array preservando quello originale.

flat

Con il metodo flat puoi appiattire array annidati.

1// flat and flatMap: flatten arrays or map + flatten in one step
2const nested = [1, [2, [3]]];
3console.log("nested:", nested); // nested: [ 1, [ 2, [ 3 ] ] ]
4console.log("nested.flat():", nested.flat()); // default depth = 1
5// nested.flat(): [ 1, 2, [ 3 ] ]
  • Questo codice dimostra il risultato dell'appiattimento di un array di un livello.
  • Poiché flat ti permette di specificare la profondità, puoi risolvere in modo flessibile l'annidamento secondo necessità.

flatMap

Il metodo flatMap applica una trasformazione a ciascun elemento e poi appiattisce automaticamente i risultati in un array monodimensionale.

1// flat and flatMap: flatten arrays or map + flatten in one step
2const words = ['hello world', 'hi'];
3console.log("words:", words); // [ 'hello world', 'hi' ]
4
5const splitWords = words.flatMap(w => w.split(' '));
6console.log("words.flatMap(w => w.split(' ')):", splitWords);
7// words.flatMap(w => w.split(' ')): [ 'hello', 'world', 'hi' ]
  • Questo codice mostra un esempio dove ogni stringa in un array viene divisa in base agli spazi e i risultati sono combinati e appiattiti in un unico array.

join

Il metodo join crea una stringa concatenando gli elementi di un array con un separatore specificato.

1// join: combine elements into a string with a separator
2const csv = ['a', 'b', 'c'].join(',');
3console.log("['a', 'b', 'c'].join(','):", csv);  // a,b,c
  • In questo codice, il metodo join viene utilizzato per convertire un array in una stringa separata da virgole.

Errori comuni

Le operazioni sugli array possono sembrare semplici a prima vista, ma ci sono diversi aspetti che possono facilmente portare a comportamenti non voluti. Molte insidie possono essere trascurate durante le operazioni quotidiane sugli array, quindi tenere a mente i seguenti punti migliorerà notevolmente l'affidabilità del tuo codice.

  • Il metodo sort() di Array ordina per confronto di stringhe per impostazione predefinita. Per ordinare correttamente i numeri, devi sempre fornire una funzione di confronto.
  • Copiare gli array (tramite slice o sintassi spread, ecc.) crea una copia superficiale. Se i tuoi array contengono oggetti, fai attenzione perché i dati originali potrebbero essere modificati involontariamente.
  • splice è un metodo distruttivo che modifica direttamente l’array, mentre slice è un metodo non distruttivo che non modifica l’array originale. È importante usarli in modo appropriato per le tue esigenze.
  • forEach non è adatto per cicli con elaborazione asincrona tramite await. Se vuoi eseguire in modo affidabile l’elaborazione asincrona in ordine, si consiglia di usare for...of.

Esempio pratico

Di seguito un esempio che combina i metodi degli array per ‘ottenere l'età totale dai dati dell'utente, estrarre coloro che hanno 30 anni o più e creare un elenco di nomi.’.

 1const users = [
 2  { name: 'Alice', age: 28 },
 3  { name: 'Bob', age: 34 },
 4  { name: 'Carol', age: 41 },
 5  { name: 'Dave', age: 19 }
 6];
 7
 8console.log("Users:", users);
 9
10// sum ages
11const totalAge = users.reduce((acc, u) => acc + u.age, 0);
12console.log("\nTotal age of all users:", totalAge);
13// Total age of all users: 122
14
15// filter and map names of users 30 and older
16const namesOver30 = users
17  .filter(u => u.age >= 30)
18  .map(u => u.name);
19
20console.log("Users aged 30 or older (names):", namesOver30);
21// Users aged 30 or older (names): [ 'Bob', 'Carol' ]
  • Combinando reduce, filter e map, puoi scrivere in modo semplice aggregazione, estrazione di condizioni e trasformazione dei dati.
  • Una simile 'pipeline di elaborazione dati' è altamente leggibile e, come stile con pochi effetti collaterali, è spesso utilizzata nelle applicazioni reali.

Riepilogo

Con gli array JavaScript, anche le operazioni di base sono ampiamente applicabili e, usando le funzioni di ordine superiore, il tuo codice diventa più conciso ed espressivo. Ci sono molti aspetti da comprendere, ma una volta che avrai imparato a usarli in modo appropriato, l’elaborazione dei dati sarà molto più fluida.

Puoi seguire l'articolo sopra utilizzando Visual Studio Code sul nostro canale YouTube. Controlla anche il nostro canale YouTube.

YouTube Video