For-Of

Edoardo Midali
Edoardo Midali

Il ciclo for-of in TypeScript rappresenta il metodo moderno e preferito per iterare sui valori di strutture dati iterabili. Introdotto in ES6, fornisce una sintassi pulita e sicura per attraversare array, stringhe, Map, Set e qualsiasi oggetto che implementi il protocollo iterabile, operando direttamente sui valori anziché su indici o chiavi.

Fondamenti e Sintassi

Il ciclo for-of itera attraverso i valori di un iterabile, assegnando ciascun valore alla variabile dichiarata. A differenza di for-in che itera sulle chiavi, for-of accede direttamente ai valori, eliminando la necessità di accesso tramite indice.

const numeri = [10, 20, 30, 40];

for (const numero of numeri) {
  console.log(numero); // 10, 20, 30, 40
}

const parola = "TypeScript";
for (const carattere of parola) {
  console.log(carattere); // T, y, p, e, ...
}

Tipizzazione Automatica

TypeScript inferisce automaticamente il tipo della variabile del ciclo basandosi sul tipo dell’iterabile, garantendo type safety senza annotazioni esplicite nella maggior parte dei casi.

interface Prodotto {
  nome: string;
  prezzo: number;
}

const prodotti: Prodotto[] = [
  { nome: "Libro", prezzo: 15 },
  { nome: "Penna", prezzo: 2 },
];

for (const prodotto of prodotti) {
  // prodotto è automaticamente tipizzato come Prodotto
  console.log(prodotto.nome.toUpperCase());
  console.log(prodotto.prezzo.toFixed(2));
}

For-Of con Array

For-of è il metodo preferito per iterare su array quando si necessita accesso ai valori. Evita problemi di for-in con proprietà aggiuntive e fornisce sintassi più pulita rispetto a cicli for classici.

const temperature = [18, 22, 25, 19, 21];

// Pulito e diretto
for (const temp of temperature) {
  console.log(`${temp}°C`);
}

// Confronto con for tradizionale
for (let i = 0; i < temperature.length; i++) {
  console.log(`${temperature[i]}°C`);
}

Iterazione su Stringhe

For-of itera correttamente su caratteri Unicode, gestendo appropriatamente code point multi-byte a differenza dell’accesso tramite indice.

const emoji = "👋🌍";

// For-of: gestisce correttamente emoji
for (const char of emoji) {
  console.log(char); // 👋, 🌍
}

// Accesso per indice: può spezzare emoji
for (let i = 0; i < emoji.length; i++) {
  console.log(emoji[i]); // Output frammentato
}

For-Of con Map

Map è iterabile e for-of restituisce coppie [chiave, valore] ad ogni iterazione. È possibile destrutturare direttamente la coppia nella dichiarazione della variabile.

const utenti = new Map<string, number>([
  ["alice", 25],
  ["bob", 30],
  ["carol", 28],
]);

for (const [nome, eta] of utenti) {
  console.log(`${nome} ha ${eta} anni`);
}

// Iterare solo su chiavi
for (const nome of utenti.keys()) {
  console.log(nome);
}

// Iterare solo su valori
for (const eta of utenti.values()) {
  console.log(eta);
}

For-Of con Set

Set supporta iterazione diretta sui valori unici contenuti, senza necessità di conversione o accesso indiretto.

const tags = new Set(["typescript", "javascript", "node"]);

for (const tag of tags) {
  console.log(tag.toUpperCase());
}

// Rimozione condizionale durante iterazione
const numeri = new Set([1, 2, 3, 4, 5, 6]);
for (const n of numeri) {
  if (n % 2 === 0) {
    numeri.delete(n); // Sicuro in iterazione Set
  }
}

Controllo di Flusso

For-of supporta completamente break e continue, permettendo controllo granulare dell’iterazione a differenza di forEach.

const valori = [5, 12, 8, 130, 44];

// Uscita anticipata con break
for (const valore of valori) {
  if (valore > 100) {
    console.log("Trovato valore grande:", valore);
    break;
  }
}

// Salto iterazione con continue
for (const valore of valori) {
  if (valore % 2 !== 0) continue;
  console.log("Valore pari:", valore);
}

For-Of con Operazioni Asincrone

For-of funziona perfettamente con async/await, permettendo iterazioni sequenziali di operazioni asincrone. Ogni iterazione attende il completamento prima di procedere alla successiva.

const ids = [1, 2, 3, 4, 5];

async function elaboraTutti() {
  for (const id of ids) {
    const dati = await fetchDati(id);
    await processaDati(dati);
    console.log(`Completato: ${id}`);
  }
}

// Iterazione parallela con for-of e Promise.all
async function elaboraParallelo() {
  const promises = [];
  for (const id of ids) {
    promises.push(fetchDati(id));
  }
  const risultati = await Promise.all(promises);

  for (const risultato of risultati) {
    processaDati(risultato);
  }
}

For-Await-Of

Per iterabili asincroni, TypeScript supporta for-await-of, che attende automaticamente la risoluzione di Promise durante l’iterazione.

async function* generatoreAsincrono() {
  yield await Promise.resolve(1);
  yield await Promise.resolve(2);
  yield await Promise.resolve(3);
}

async function elabora() {
  for await (const valore of generatoreAsincrono()) {
    console.log(valore); // 1, 2, 3 (in sequenza)
  }
}

// Utile per stream e lettura file
async function leggiStream(stream: ReadableStream) {
  for await (const chunk of stream) {
    processaChunk(chunk);
  }
}

Destrutturazione nel Ciclo

For-of permette destrutturazione diretta nella dichiarazione della variabile, utile per array di tuple o oggetti.

const coordinate: [number, number][] = [
  [10, 20],
  [30, 40],
  [50, 60],
];

for (const [x, y] of coordinate) {
  console.log(`X: ${x}, Y: ${y}`);
}

const utenti = [
  { nome: "Anna", ruolo: "admin" },
  { nome: "Marco", ruolo: "user" },
];

for (const { nome, ruolo } of utenti) {
  console.log(`${nome} è ${ruolo}`);
}

Oggetti Iterabili Personalizzati

Qualsiasi oggetto può essere reso iterabile implementando il metodo Symbol.iterator, permettendo uso con for-of.

class Range {
  constructor(
    private inizio: number,
    private fine: number,
    private step: number = 1
  ) {}

  *[Symbol.iterator]() {
    for (let i = this.inizio; i <= this.fine; i += this.step) {
      yield i;
    }
  }
}

const range = new Range(1, 10, 2);
for (const num of range) {
  console.log(num); // 1, 3, 5, 7, 9
}

Entries, Keys, Values

Array forniscono metodi che restituiscono iteratori specializzati, utili quando servono indici oltre ai valori.

const frutti = ["mela", "banana", "arancia"];

// entries(): iteratore [indice, valore]
for (const [indice, frutto] of frutti.entries()) {
  console.log(`${indice}: ${frutto}`);
}

// keys(): iteratore degli indici
for (const indice of frutti.keys()) {
  console.log(`Indice: ${indice}`);
}

// values(): iteratore dei valori (equivalente a iterare l'array direttamente)
for (const frutto of frutti.values()) {
  console.log(frutto);
}

Performance

For-of ha performance comparabile a for tradizionale per la maggior parte dei casi d’uso, con overhead minimo. Per array enormi in scenari critici, for classico può essere marginalmente più veloce.

const grande = new Array(1000000).fill(0);

// Performance simili nella pratica
console.time("for-of");
for (const n of grande) {
  n * 2;
}
console.timeEnd("for-of");

console.time("for");
for (let i = 0; i < grande.length; i++) {
  grande[i] * 2;
}
console.timeEnd("for");

For-Of vs For-In vs ForEach

Ogni costrutto ha casi d’uso appropriati: for-of per valori di iterabili, for-in per chiavi di oggetti, forEach per effetti collaterali senza necessità di controllo di flusso.

const array = [1, 2, 3];

// for-of: valori, supporta break/continue
for (const val of array) {
  if (val === 2) break;
  console.log(val);
}

// for-in: indici (sconsigliato per array)
for (const idx in array) {
  console.log(idx); // "0", "1", "2" (stringhe)
}

// forEach: nessun controllo di flusso
array.forEach((val) => {
  // non può usare break
  console.log(val);
});

TypedArray e Buffer

For-of funziona con TypedArray e Buffer, strutture comuni per manipolazione binaria.

const bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);

for (const byte of bytes) {
  console.log(byte.toString(16)); // 48, 65, 6c, 6c, 6f
}

// Con Node.js Buffer
const buffer = Buffer.from("Hello");
for (const byte of buffer) {
  console.log(byte); // 72, 101, 108, 108, 111
}

Generatori e Lazy Evaluation

For-of eccelle con generatori, permettendo iterazione lazy di sequenze potenzialmente infinite con valutazione on-demand.

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

// Iterazione controllata di sequenza infinita
let count = 0;
for (const num of fibonacci()) {
  console.log(num);
  if (++count === 10) break; // Primi 10 numeri Fibonacci
}

Conclusioni

Il ciclo for-of rappresenta lo standard moderno per iterazione in TypeScript, offrendo sintassi pulita, type safety automatica, e compatibilità con diverse strutture dati. Supporto completo per controllo di flusso, operazioni asincrone, e destrutturazione lo rendono versatile per la maggior parte degli scenari di iterazione. Rispetto a for-in e forEach, for-of bilancia leggibilità con funzionalità, risultando appropriato come scelta predefinita per iterare su valori di collezioni.