Istruzioni di Ciclo in Javascript

Per comprendere le istruzioni condizionali, abbiamo immaginato l'interprete JavaScript seguire un percorso ramificato attraverso il codice sorgente. Le istruzioni di ciclo sono quelle che piegano quel percorso su se stesso per ripetere porzioni del codice. JavaScript ha cinque istruzioni di ciclo: while, do/while, for, for/of e for/in.

In questa lezione, esploreremo ciascuna di queste istruzioni di ciclo a sua volta. Un uso comune per i cicli è iterare sugli elementi di un array che studieremo nelle prossime lezioni.

Concetti Chiave
  • Le istruzioni di ciclo in JavaScript permettono di eseguire blocchi di codice ripetutamente fino a quando una condizione non è più vera.
  • L'istruzione while esegue un blocco di codice finché una condizione è vera.
  • L'istruzione do/while garantisce che il blocco di codice venga eseguito almeno una volta prima di verificare la condizione.
  • L'istruzione for è utile per cicli con contatori, permettendo di inizializzare, testare e incrementare una variabile in un'unica riga.
  • L'istruzione for/of è utilizzata per iterare su oggetti iterabili come array e stringhe.
  • L'istruzione for/in è utilizzata per iterare sulle proprietà di un oggetto.

Istruzione while

Proprio come l'istruzione if è l'istruzione condizionale di base di JavaScript, l'istruzione while è il ciclo di base di JavaScript.

Essa ha la seguente sintassi:

while (espressione)
    istruzione

Per eseguire un'istruzione while, l'interprete prima valuta espressione. Se il valore dell'espressione è falso, allora l'interprete salta l'istruzione che serve come corpo del ciclo e passa alla prossima istruzione nel programma. Se, dall'altro lato, l'espressione è vera, l'interprete esegue l'istruzione e ripete, saltando di nuovo all'inizio del ciclo e valutando nuovamente l'espressione. Un altro modo per dire questo è che l'interprete esegue l'istruzione ripetutamente finché (while) l'espressione è vera. Si noti che è possibile creare un ciclo infinito con la sintassi while(true).

Solitamente, non si vuole che JavaScript esegua esattamente la stessa operazione più e più volte. In quasi ogni ciclo, una o più variabili cambiano ad ogni iterazione del ciclo. Dato che le variabili cambiano, le azioni eseguite dall'esecuzione dell'istruzione possono differire ogni volta attraverso il ciclo. Inoltre, se la variabile o le variabili che cambiano sono coinvolte nell'espressione, il valore dell'espressione può essere diverso ogni volta attraverso il ciclo. Questo è importante; altrimenti, un'espressione che inizia truthy non cambierebbe mai, e il ciclo non finirebbe mai!

Ecco un esempio di un ciclo while che stampa i numeri da 0 a 9:

let contatore = 0;
while(contatore < 10) {
    console.log(contatore);
    contatore++;
}

Come si può vedere, la variabile contatore inizia da 0 ed è incrementata ogni volta che il corpo del ciclo viene eseguito. Una volta che il ciclo è stato eseguito 10 volte, l'espressione diventa false (cioè, la variabile contatore non è più minore di 10), l'istruzione while finisce, e l'interprete può passare alla prossima istruzione nel programma. Molti cicli hanno una variabile contatore come contatore. I nomi delle variabili i, j, e k sono comunemente usati come contatori di ciclo, anche se si dovrebbero usare nomi più descrittivi se rendono il codice più facile da capire.

Istruzione do/while

Il loop do/while è simile a un loop while, eccetto che l'espressione del loop viene testata alla fine del loop piuttosto che all'inizio. Questo significa che il corpo del loop viene sempre eseguito almeno una volta. La sintassi è:

do
    istruzione
while (espressione);

Il loop do/while è usato meno comunemente del suo cugino while, in pratica, è piuttosto raro essere certi di volere che un loop venga eseguito almeno una volta. Ecco un esempio di un loop do/while:

function stampaArray(a) {
    let lunghezza = a.length, i = 0;
    if (lunghezza === 0) {
        console.log("Array Vuoto");
    } else {
        do {
            console.log(a[i]);
        } while(++i < lunghezza);
    }
}

Ci sono un paio di differenze sintattiche tra il loop do/while e il loop while ordinario. Prima di tutto, il loop do richiede sia la parola chiave do (per contrassegnare l'inizio del loop) che la parola chiave while (per contrassegnare la fine e introdurre la condizione del loop). Inoltre, il loop do deve essere sempre terminato con un punto e virgola. Il loop while non ha bisogno di un punto e virgola se il corpo del loop è racchiuso tra parentesi graffe.

Istruzione for

L'istruzione for fornisce un costrutto di ciclo che è spesso più conveniente dell'istruzione while. L'istruzione for semplifica i cicli che seguono un pattern comune. La maggior parte dei cicli ha una variabile contatore di qualche tipo. Questa variabile viene inizializzata prima che il ciclo inizi e viene testata prima di ogni iterazione del ciclo. Infine, la variabile contatore viene incrementata o altrimenti aggiornata alla fine del corpo del ciclo, proprio prima che la variabile venga testata di nuovo. In questo tipo di ciclo, l'inizializzazione, il test e l'aggiornamento sono le tre manipolazioni cruciali di una variabile di ciclo. L'istruzione for codifica ognuna di queste tre manipolazioni come un'espressione e rende quelle espressioni una parte esplicita della sintassi del ciclo:

for(inizializza ; test ; incrementa)
    istruzione

inizializza, test e incrementa sono tre espressioni (separate da punti e virgola) che sono responsabili dell'inizializzazione, del test e dell'incremento della variabile di ciclo. Mettendole tutte nella prima riga del ciclo rende facile capire cosa sta facendo un ciclo for e previene errori come dimenticare di inizializzare o incrementare la variabile di ciclo.

Il modo più semplice per spiegare come funziona un ciclo for è mostrare il ciclo while equivalente:

inizializza;
while(test) {
    istruzione
    incrementa;
}

In altre parole, l'espressione inizializza viene valutata una volta, prima che il ciclo inizi. Per essere utile, questa espressione deve avere effetti collaterali (di solito un'assegnazione). JavaScript permette anche a inizializza di essere un'istruzione di dichiarazione di variabile così che si possa dichiarare e inizializzare un contatore di ciclo allo stesso tempo. L'espressione test viene valutata prima di ogni iterazione e controlla se il corpo del ciclo viene eseguito. Se test viene valutata a un valore truthy, l'istruzione che è il corpo del ciclo viene eseguita. Infine, l'espressione incrementa viene valutata. Ancora, questa deve essere un'espressione con effetti collaterali per essere utile. Generalmente, è o un'espressione di assegnazione, o usa gli operatori ++ o --.

È possibile stampare i numeri da 0 a 9 con un ciclo for come il seguente. Si confronti con il ciclo while equivalente mostrato nella sezione precedente:

for(let contatore = 0; contatore < 10; contatore++) {
    console.log(contatore);
}

I cicli possono diventare molto più complessi di questo semplice esempio, ovviamente, e a volte più variabili cambiano con ogni iterazione del ciclo. Questa situazione è l'unico posto dove l'operatore virgola è comunemente usato in JavaScript; fornisce un modo per combinare più espressioni di inizializzazione e incremento in una singola espressione adatta per l'uso in un ciclo for:

let i, j, somma = 0;
for(i = 0, j = 10 ; i < 10 ; i++, j--) {
    somma += i * j;
}

In tutti i nostri esempi di ciclo finora, la variabile di ciclo è stata numerica. Questo è abbastanza comune ma non è necessario. Il seguente codice usa un ciclo for per attraversare una struttura dati lista collegata e restituire l'ultimo oggetto nella lista (cioè, il primo oggetto che non ha una proprietà next):

function coda(o) {                          // Restituisce la coda della lista collegata o
    for(; o.next; o = o.next) /* vuoto */ ; // Attraversa mentre o.next è truthy
    return o;
}

Si noti che questo codice non ha espressione inizializza. Qualsiasi delle tre espressioni può essere omessa da un ciclo for, ma i due punti e virgola sono richiesti. Se si omette l'espressione test, il ciclo si ripete per sempre, e for(;;) è un altro modo di scrivere un ciclo infinito, come while(true).

Istruzione for/of

ES6 definisce una nuova istruzione di ciclo: for/of. Questo nuovo tipo di ciclo usa la parola chiave for ma è un tipo di ciclo completamente diverso dal ciclo for regolare.

Il ciclo for/of funziona con oggetti iterabili. Spiegheremo esattamente cosa significa per un oggetto essere iterabile nelle prossime lezioni, ma per questa lezione è sufficiente sapere che array, stringhe, set e mappe sono iterabili: rappresentano una sequenza o un insieme di elementi attraverso i quali è possibile eseguire un ciclo o iterare usando un ciclo for/of.

Ecco, ad esempio, come è possibile usare for/of per eseguire un ciclo attraverso gli elementi di un array di numeri e calcolare la loro somma:

Ecco, ad esempio, come possiamo usare for/of per eseguire un ciclo attraverso gli elementi di un array di numeri e calcolare la loro somma:

let dati = [1, 2, 3, 4, 5, 6, 7, 8, 9], somma = 0;
for(let elemento of dati) {
    somma += elemento;
}
somma       // => 45

Superficialmente, la sintassi sembra un ciclo for regolare: la parola chiave for è seguita da parentesi che contengono dettagli su cosa dovrebbe fare il ciclo. In questo caso, le parentesi contengono una dichiarazione di variabile (o, per variabili che sono già state dichiarate, semplicemente il nome della variabile) seguita dalla parola chiave of e un'espressione che valuta un oggetto iterabile, come l'array dati in questo caso. Come con tutti i cicli, il corpo di un ciclo for/of segue le parentesi, tipicamente tra parentesi graffe.

Nel codice appena mostrato, il corpo del ciclo viene eseguito una volta per ogni elemento dell'array dati. Prima di ogni esecuzione del corpo del ciclo, l'elemento successivo dell'array viene assegnato alla variabile elemento. Gli elementi dell'array vengono iterati in ordine dal primo all'ultimo.

Gli array vengono iterati "live", i cambiamenti fatti durante l'iterazione possono influenzare il risultato dell'iterazione. Se si modifica il codice precedente aggiungendo la riga dati.push(somma); all'interno del corpo del ciclo, allora si crea un ciclo infinito perché l'iterazione non può mai raggiungere l'ultimo elemento dell'array.

for/of con oggetti

Gli oggetti non sono (di default) iterabili. Il tentativo di utilizzare for/of su un oggetto regolare genera un TypeError a runtime:

let o = { x: 1, y: 2, z: 3 };
for(let elemento of o) { // Genera TypeError perché o non è iterabile
    console.log(elemento);
}

Se si vuole iterare attraverso le proprietà di un oggetto, si può utilizzare il ciclo for/in, o utilizzare for/of con il metodo Object.keys():

let o = { x: 1, y: 2, z: 3 };
let chiavi = "";
for(let k of Object.keys(o)) {
    chiavi += k;
}
chiavi  // => "xyz"

Questo funziona perché Object.keys() restituisce un array di nomi di proprietà per un oggetto, e gli array sono iterabili con for/of. Si noti anche che questa iterazione delle chiavi di un oggetto non è live come nell'esempio dell'array sopra, modifiche all'oggetto o fatte nel corpo del ciclo non avranno effetto sull'iterazione. Se non interessano le chiavi di un oggetto, si può anche iterare attraverso i loro valori corrispondenti in questo modo:

let somma = 0;
for(let v of Object.values(o)) {
    somma += v;
}
somma // => 6

E se interessano sia le chiavi che ai valori delle proprietà di un oggetto, si può utilizzare for/of con Object.entries() e l'assegnazione destrutturata:

let coppie = "";
for(let [k, v] of Object.entries(o)) {
    coppie += k + v;
}
coppie  // => "x1y2z3"

Object.entries() restituisce un array di array, dove ogni array interno rappresenta una coppia chiave/valore per una proprietà dell'oggetto. Utilizziamo l'assegnazione destrutturata in questo esempio di codice per spacchettare quegli array interni in due variabili individuali.

for/of con le stringhe

Le stringhe sono iterabili carattere per carattere in ES6:

let frequenza = {};
for(let lettera of "mississippi") {
    if (frequenza[lettera]) {
        frequenza[lettera]++;
    } else {
        frequenza[lettera] = 1;
    }
}
frequenza   // => {m: 1, i: 4, s: 4, p: 2}

Si noti che le stringhe vengono iterate per codepoint Unicode, non per carattere UTF-16. La stringa "I❤!" ha una .length di 4 (perché il carattere emoji richiede due caratteri UTF-16 per essere rappresentato). Ma se si itera quella stringa con for/of, il corpo del ciclo verrà eseguito tre volte, una volta per ciascuno dei tre codepoint "I", "❤", e "!".

for/of con Set e Map

Le classi integrate ES6 Set e Map sono iterabili. Quando si itera un Set con for/of, il corpo del ciclo viene eseguito una volta per ogni elemento del set. Si potrebbe usare codice come questo per stampare le parole uniche in una stringa di testo:

let testo = "Na na na na na na na na Batman!";
let setParole = new Set(testo.split(" "));
let uniche = [];
for(let parola of setParole) {
    uniche.push(parola);
}
uniche // => ["Na", "na", "Batman!"]

Le Map sono un caso interessante perché l'iteratore per un oggetto Map non itera le chiavi della Map, o i valori della Map, ma le coppie chiave/valore. Ogni volta attraverso l'iterazione, l'iteratore restituisce un array il cui primo elemento è una chiave e il cui secondo elemento è il valore corrispondente. Data una Map m, si potrebbe iterare e destrutturare le sue coppie chiave/valore così:

let m = new Map([[1, "uno"]]);
for(let [chiave, valore] of m) {
    chiave    // => 1
    valore    // => "uno"
}

Istruzione for/in

Un loop for/in somiglia molto a un loop for/of, con la parola chiave of cambiata in in. Mentre un loop for/of richiede un oggetto iterabile dopo of, un loop for/in funziona con qualsiasi oggetto dopo in. Il loop for/of è nuovo in ES6, ma for/in fa parte di JavaScript fin dall'inizio (ecco perché ha una sintassi dal suono più naturale).

L'istruzione for/in cicla attraverso i nomi delle proprietà di un oggetto specificato. La sintassi appare così:

for (variabile in oggetto)
    istruzione

variabile tipicamente nomina una variabile, ma può essere una dichiarazione di variabile o qualsiasi cosa adatta come lato sinistro di un'espressione di assegnazione. oggetto è un'espressione che valuta un oggetto. Come sempre, istruzione è l'istruzione o il blocco di istruzioni che serve come corpo del loop.

E si potrebbe usare un loop for/in così:

for(let p in o) {      // Assegna i nomi delle proprietà di o alla variabile p
    console.log(o[p]); // Stampa il valore di ogni proprietà
}

Per eseguire un'istruzione for/in, l'interprete JavaScript prima valuta l'espressione oggetto. Se valuta null o undefined, l'interprete salta il loop e passa alla prossima istruzione. L'interprete ora esegue il corpo del loop una volta per ogni proprietà enumerabile dell'oggetto. Prima di ogni iterazione, tuttavia, l'interprete valuta l'espressione variabile e le assegna il nome della proprietà (un valore stringa).

Nota che la variabile nel loop for/in può essere un'espressione arbitraria, purché valuti qualcosa di adatto per il lato sinistro di un'assegnazione. Questa espressione viene valutata ogni volta attraverso il loop, il che significa che può valutare diversamente ogni volta. Per esempio, si può usare codice come il seguente per copiare i nomi di tutte le proprietà dell'oggetto in un array:

let o = { x: 1, y: 2, z: 3 };
let a = [], i = 0;
for(a[i++] in o) /* vuoto */;

Gli array JavaScript sono semplicemente un tipo specializzato di oggetto, e gli indici degli array sono proprietà dell'oggetto che possono essere enumerate con un loop for/in. Per esempio, seguendo il codice precedente con questa riga enumera gli indici dell'array 0, 1, e 2:

for(let i in a) console.log(i);

Si trova che una fonte comune di bug nel codice sia l'uso accidentale di for/in con gli array quando si intende usare for/of. Quando si lavora con gli array, quasi sempre si vuole usare for/of invece di for/in.

Il loop for/in non enumera effettivamente tutte le proprietà di un oggetto. Non enumera le proprietà i cui nomi sono simboli. E delle proprietà i cui nomi sono stringhe, cicla solo sulle proprietà enumerabili. I vari metodi built-in definiti dal JavaScript core non sono enumerabili. Tutti gli oggetti hanno un metodo toString(), per esempio, ma il loop for/in non enumera questa proprietà toString. In aggiunta ai metodi built-in, molte altre proprietà degli oggetti built-in sono non-enumerabili. Tutte le proprietà e i metodi definiti dal nostro codice sono enumerabili, per default.

Le proprietà ereditate enumerabili sono anche enumerate dal loop for/in. Questo significa che se si usano loop for/in e anche codice che definisce proprietà che sono ereditate da tutti gli oggetti, allora il loop potrebbe non comportarsi nel modo che ci si aspetta. Per questa ragione, molti programmatori preferiscono usare un loop for/of con Object.keys() invece di un loop for/in.

Se il corpo di un loop for/in cancella una proprietà che non è ancora stata enumerata, quella proprietà non sarà enumerata. Se il corpo del loop definisce nuove proprietà sull'oggetto, quelle proprietà possono o non possono essere enumerate.