Gli oggetti consentono la memorizzazione di una collezione di valori associati alle rispettive chiavi.
Ma spesso abbiamo bisogno di una collezione ordinata, dove abbiamo un primo, un secondo, un terzo elemento e cos via. Ad esempio, abbiamo bisogno di memorizzare una lista di cose: utenti, beni, elementi HTML etc.
Non conveniente utilizzare un oggetto per questo tipo di lavori, poich non avremmo alcun metodo per gestire lordine degli elementi. Non possiamo inserire una nuova propriet tra due gi esistenti. Gli oggetti non sono stati pensati per questo scopo.
Esistono delle speciali strutture dati chiamate Array, che consentono la memorizzazione di collezioni ordinate.
Dichiarazione
Ci sono due differenti sintassi per la creazioni di un array vuoto:
let arr = new Array();
let arr = [];
Nella maggioranza dei casi, la seconda sintassi quella preferita. Possiamo gi fornire degli elementi da inserire, allinterno delle parentesi quadre:
let fruits = ["Apple", "Orange", "Plum"];
Gli elementi di un array sono numerati a partire dallo zero.
Possiamo ottenere un elemento tramite il suo numero di indice:
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits[0] ); // Apple
alert( fruits[1] ); // Orange
alert( fruits[2] ); // Plum
Possiamo rimpiazzare un elemento:
fruits[2] = 'Pear'; // ora ["Apple", "Orange", "Pear"]
o aggiungerne uno nuovo:
fruits[3] = 'Lemon'; // ora ["Apple", "Orange", "Pear", "Lemon"]
Il numero degli elementi dellarray length:
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits.length ); // 3
Possiamo anche utilizzare alert per mostrare lintero array.
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits ); // Apple,Orange,Plum
Un array pu memorizzare elementi di qualsiasi tipo.
Ad esempio:
// insieme di valori
let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];
// prende l'oggetto all'indice 1 e ne mostra il nome
alert( arr[1].name ); // John
// prende la funzione all'indice 3 e la esegue
arr[3](); // hello
Gli array, proprio come gli oggetti, possono terminare con una virgola:
let fruits = [
"Apple",
"Orange",
"Plum",
];
La virgola pendente rende pi semplice inserire/rimuovere elementi, perch tutte le linee seguono la stessa struttura.
I metodi pop/push, shift/unshift
Una queue (coda) una delle pi comuni applicazioni di un array. In ambito informatico, questa una collezione ordinata che consente due operazioni:
pushinserisce un elemento in coda.shiftper estrarre un elemento dallinizio, e scorrere in avanti la coda, di modo che il secondo elemento diventa il primo.
Gli array supportano entrambe le operazioni.
Nella pratica non strano incontrare questo tipo di array. Ad esempio una coda di messaggi che devono essere mostrati sullo schermo.
Esiste un altro caso duso degli array la struttura dati chiamata stack.
Questa supporta due operazioni:
pushaggiunge un elemento in coda.popestrae un elemento dalla coda.
Quindi gli elementi vengono sempre aggiunti o presi dalla fine.
Uno stack viene spesso illustrato come un pacco di carte: le nuove carte vengono aggiunte sempre in cima o da l estratte:
Negli gli stack, lultimo elemento inserito viene prelevato per primo. Questo comportamento viene definito LIFO (Last-In-First-Out). Nel caso delle code, il comportamento viene chiamato FIFO (First-In-First-Out).
Gli array in JavaScript possono implementare sia una queue che uno stack. C la possibilit di aggiungere/rimuovere elementi sia in cima che in coda.
In informatica questa struttura dati si chiama deque.
Metodi che operano sulla coda di un array:
pop-
Estrae lultimo elemento dellarray e lo ritorna:
let fruits = ["Apple", "Orange", "Pear"]; alert( fruits.pop() ); // rimuove "Pear" e lo ritorna con alert alert( fruits ); // Apple, Orange push-
Inserisce lelemento in coda allarray:
let fruits = ["Apple", "Orange"]; fruits.push("Pear"); alert( fruits ); // Apple, Orange, PearLa chiamata
fruits.push(...)equivalente afruits[fruits.length] = ....
Metodi che operano sullinizio dellarray:
shift-
Estrae il primo elemento dellarray e lo ritorna:
let fruits = ["Apple", "Orange", "Pear"]; alert( fruits.shift() ); // rimuove Apple e lo ritorna con alert alert( fruits ); // Orange, Pear unshift-
Aggiunge lelemento alla testa dellarray:
let fruits = ["Orange", "Pear"]; fruits.unshift('Apple'); alert( fruits ); // Apple, Orange, Pear
I metodi push e unshift possono aggiungere anche pi elementi in una volta sola:
let fruits = ["Apple"];
fruits.push("Orange", "Peach");
fruits.unshift("Pineapple", "Lemon");
// ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
alert( fruits );
Internamente
Un array uno speciale tipo di oggetto. Le parentesi quadre utilizzate per accedere alla propriet arr[0] derivano dalla sintassi utilizzata con gli oggetti. Questo equivale a obj[key], dove arr loggetto, mentre i numeri vengono utilizzati come chiavi.
Inoltre estendono gli oggetti fornendo speciali metodi per operare ordinatamente su collezioni di dati, e contengono la propriet length. Ma internamente rimane sempre un oggetto.
Ricordate, ci sono solo 7 tipi di base in JavaScript. Gli array sono oggetti e si comportano come tali.
Ad esempio, vengono copiati per riferimento:
let fruits = ["Banana"]
let arr = fruits; // copia per riferimento (due variabili fanno riferimento allo stesso array)
alert( arr === fruits ); // true
arr.push("Pear"); // modifica l'array per riferimento
alert( fruits ); // Banana, Pear - ora sono 2 elementi
Ma ci che li rende realmente speciali la loro rappresentazione interna. Il motore prova a memorizzare gli elementi in aree di memoria contigue, uno dopo laltro, proprio come nelle illustrazioni di questo capitolo, e ci sono anche altre ottimizzazioni per rendere gli array molto veloci.
Se iniziamo a trattare gli array come oggetti ordinari tutte le ottimizzazioni vengono meno.
Ad esempio, tecnicamente possiamo fare:
let fruits = []; // crea una array
fruits[99999] = 5; // assegna una propriet con indice maggiore della sua lunghezza
fruits.age = 25; // crea una propriet con un nome a scelta
Questo possibile, perch gli array sono comunque degli oggetti. Possiamo aggiungervi qualsiasi propriet.
Ma lengine si accorger che stiamo utilizzando gli array come comuni oggetti. Le ottimizzazioni specifiche per gli array non sono studiate per questi casi, e verranno quindi disattivate.
I modi per usare incorrettamente un array:
- Aggiungere una propriet non numerica, come
arr.test = 5. - Creare buchi: aggiungendo
arr[0]e poiarr[1000](lasciando spazio vuoto tra di loro). - Riempire larray nellordine inverso, ad esempio
arr[1000],arr[999].
E molto conveniente pensare agli array come delle speciali strutture utili a lavorare con dati ordinati. Forniscono speciali metodi utili a questo scopo, inoltre sono attentamente ottimizzati dal motore JavaScript per lavorare con dati ordinati e memorizzati in posizioni contigue. Quindi se doveste aver bisogno di utilizzare una propriet con una chiave arbitraria, molto probabilmente un oggetto soddisfer le vostre necessit.
Performance
I metodi push/pop vengono eseguiti rapidamente, mentre shift/unshift sono pi lenti.
Perch pi veloce eseguire operazioni sulla coda degli array rispetto a quelle sulla testa? Andiamo a vedere cosa accade durante lesecuzione:
fruits.shift(); // prende 1 elemento dall'inizio
Non sufficiente prelevare e rimuovere lelemento con lindice 0. Gli altri elementi dovranno essere rinumerati.
Loperazione di shift deve seguire 3 passi:
- Rimuovere lelemento con indice
0. - Spostare tutti gli elementi a sinistra, rinumerare gli indici da
1a0, da2a1e cosi via. - Aggiornare la propriet
length.
Maggiore sar il numero di elementi, maggiore sar il tempo richiesto, e maggiori saranno il numero di operazioni in memoria.
Una cosa simile accade con unshift: per aggiungere un elemento in testa allarray, abbiamo prima bisogno di spostare tutti gli elementi a destra e aggiornare gli indici.
Invece con push/pop? Non richiedono lo spostamento di nulla in memoria. Per poter prelevare un elemento dalla coda, il metodo pop pulisce lindirizzo e decrementa length.
Le azioni eseguite da pop:
fruits.pop(); // prende 1 elemento dalla fine
Il metodo pop non richiede spostamenti, perch ogni elemento mantiene il suo indice. Questo il motivo per cui risulta essere unoperazione molto veloce.
Una cosa simile accade con il metodo push.
Cicli
Uno dei modi pi vecchi per eseguire cicli sugli elementi di un array il for utilizzando gli indici:
let arr = ["Apple", "Orange", "Pear"];
for (let i = 0; i < arr.length; i++) {
alert( arr[i] );
}
Per gli array c unaltra forma di ciclo, for..of:
let fruits = ["Apple", "Orange", "Plum"];
// itera sugli elementi dell'array
for (let fruit of fruits) {
alert( fruit );
}
Il ciclo for..of non fornisce il numero dindice dellelemento corrente, solo il suo valore; in molte situazioni questo pi che sufficiente. E pi breve.
Tecnicamente, poich gli array sono oggetti, anche possibile utilizzare for..in:
let arr = ["Apple", "Orange", "Pear"];
for (let key in arr) {
alert( arr[key] ); // Apple, Orange, Pear
}
Non comunque unottima idea. Si possono verificare diversi errori:
-
Il ciclo
for..initera su tutte le propriet, non solo su quelle numeriche.Ci sono anche degli oggetti chiamati array-like (simili ad array), nei browser e in altri ambienti, che assomigliano ad array. Infatti come propriet possiedono
lengthe degli indici, ma allo stesso tempo contengono propriet e metodi di tipo non numerico, di cui solitamente non abbiamo bisogno. Il ciclofor..inli passer tutti. Quindi se stiamo utilizzando degli oggetti array-like, questi extra potrebbero rivelarsi un problema. -
Il ciclo
for..inottimizzato per gli oggetti generici, non gli array, e pu risultare quindi 10-100 volte pi lento. Ovviamente rimane comunque unoperazione molto veloce. Pu essere un problema solo in caso si verifichino ingorghi.
Generalmente, non dovremmo utilizzare for..in per gli array.
Una parola riguardo length
La propriet length si aggiorna automaticamente ad ogni modifica. Volendo essere precisi non ne rappresenta la lunghezza, ma lultimo indice numerico pi uno.
Ad esempio, un singolo elemento con un indice molto alto fornisce un altrettanto grande lunghezza:
let fruits = [];
fruits[123] = "Apple";
alert( fruits.length ); // 124
Ovviamente questo non il modo corretto di utilizzare un array.
Unaltra cosa interessante riguardo la propriet length che sovrascrivibile.
Se provassimo ad incrementarla manualmente, non accadrebbe nulla di interessante. Se invece la decrementiamo, larray verr troncato. Il processo irreversibile. Vediamo un esempio:
let arr = [1, 2, 3, 4, 5];
arr.length = 2; // tronca a 2 elementi
alert( arr ); // [1, 2]
arr.length = 5; // ritorna alla lunghezza precedente
alert( arr[3] ); // undefined: i valori non vengono ritornati
Quindi il modo pi semplice per ripulire un array : arr.length = 0;.
new Array()
C un ulteriore sintassi per creare un array:
let arr = new Array("Apple", "Pear", "etc");
Viene utilizzata raramente, le parentesi [] risultano pi brevi. Anche se c una caratteristica interessante che va osservata.
Se utilizziamo new Array con un solo argomento di tipo numerico, allora verr creato un array vuoto, ma con lunghezza data.
Quindi vediamo come ci si potrebbe sparare da soli al piede:
let arr = new Array(2); // creer un array [2] ?
alert( arr[0] ); // undefined! nessun elemento.
alert( arr.length ); // length 2
Nel codice sopra, new Array(number) ha tutti gli elementi undefined.
Per evitare queste spiacevoli sorprese, solitamente si utilizzano le parentesi quadre, a meno di non sapere davvero che cosa si sta facendo.
Array multi-dimensionali
Gli array possono contenere oggetti che sono a loro volta array. Possiamo quindi utilizzare questa propriet per creare array multi-dimensionali, ad esempio per memorizzare matrici:
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
alert( matrix[1][1] ); // l'elemento centrale
toString
Gli array hanno una propria implementazione del metodo toString, il quale ritorna la lista degli elementi separati da una virgola.
Ad esempio:
let arr = [1, 2, 3];
alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true
Proviamo anche:
alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"
Gli array non possiedono Symbol.toPrimitive, e nemmeno valueOf; implementano solamente la conversione toString, quindi [] diventa una stringa vuota, [1] diventa "1" e [1,2] diventa "1,2".
Quando loperatore di somma binaria "+" aggiunge qualcosa ad una stringa, converte tutto a stringa, quindi lesempio di prima sar equivalente a:
alert( "" + 1 ); // "1"
alert( "1" + 1 ); // "11"
alert( "1,2" + 1 ); // "1,21"
Non confrontate gli array con ==
In JavaScript gli array, a differenza di altri linguaggi di programmazione, non dovrebbero essere confrontati con loperatore ==.
Questo operatore non offre alcun tipo di trattamento speciale per gli array: li considera come oggetti.
Ricordando velocemente le regole:
- Due oggetti sono uguali con
==solamente se fanno riferimento allo stesso oggetto. - Se uno dei due argomenti forniti alloperatore
==un oggetto, e laltro un tipo primitivo, allora loggetto viene convertito a primitivo, come spiegato nel capitolo Conversione da oggetto a primitivi. - Con leccezione di
nulleundefinedche sono uguali solamente tra di loro.
Il confronto stretto con loperatore === ancora pi semplice, poich non converte i tipi.
Quindi, se confrontiamo array con ==, non saranno mai equivalenti, a meno ch non confrontiamo due variabili che fanno riferimento allo stesso array.
Ad esempio:
alert( [] == [] ); // false
alert( [0] == [0] ); // false
Questi array sono tecnicamente oggetti differenti. Quindi non si equivalgono. Loperatore == non effettua il confronto elemento per elemento.
Anche il confronto con tipi primitivi potrebbe dare risultati strani:
alert( 0 == [] ); // true
alert('0' == [] ); // false
Qui, in entrambi i casi, stiamo confrontando un tipo primitivo con un array. Quindi larray [] viene convertito in tipo primitivo per effettuare il confronto e diventa una stringa vuota ''.
Successivamente il processo di confronto procede come descritto nel capitolo Conversione di tipi:
// dopo averlo convertito, l'array [] equivale a ''
alert( 0 == '' ); // true, poich '' viene convertito nel numero 0
alert('0' == '' ); // false, nessuna conversione di tipo, sono stringhe differenti
Quindi, come possiamo confrontare gli array?
Molto semplice: non utilizzando loperatore==. Invece, vanno confrontati con un ciclo che confronta ogni elemento dei due array, oppure utilizzando uno dei metodi di iterazione che vedremo nel prossimo capitolo.
Riepilogo
Gli array sono uno speciale tipo di oggetto, studiati per immagazzinare e gestire collezioni ordinate di dati.
-
La dichiarazione:
// parentesi quadre (usuale) let arr = [item1, item2...]; // new Array (eccezionalmente raro) let arr = new Array(item1, item2...);La chiamata a
new Array(number)crea un array con la lunghezza data, ma senza elementi. -
La propriet
lengthla lunghezza dellarray; in realt, per essere precisi, contiene lindice dellultimo elemento pi uno. Questo valore viene aggiornato automaticamente. -
Se decrementiamo manualmente
length, larray viene troncato.
Possiamo eseguire sugli arrays le seguenti operazioni:
push(...items)aggiungeitemsin coda.pop()rimuove un elemento dalla coda e lo ritorna.shift()rimuove un elemento dalla testa e lo ritorna.unshift(...items)aggiunge un elemento alla testa.
Per iterare sugli elementi di un array:
for (let i = 0; i < arr.length; i++)il pi veloce, compatibile con i vecchi browsers.for (let item of arr)la moderna sintassi, solo per gli elementi.for (let i in arr)da non usare.
Per confrontare gli array, non utilizzate loperatore == (lo stesso vale per >, < ecc), poich non riservano alcun trattamento speciale per gli array. Li trattano come degli oggetti comuni, e solitamente non quello che vogliamo.
Piuttosto si possono utilizzare i cicli come for..of per confrontare ogni elemento dei due array.
Continueremo con lo studio degli array e di altri metodi per aggiungere, rimuovere, estrarre elementi ed ordinarli, nel prossimo capitolo Metodi per gli array.
Commenti
<code>, per molte righe includile nel tag<pre>, per pi di 10 righe utilizza una sandbox (plnkr, jsbin, codepen)