Torna al blog

ECMAScript 2024: le novità più importanti per JavaScript

Scopri tutte le nuove funzionalità introdotte con ECMAScript 2024: type annotations, set operations, iterator helpers e altre novità che miglioreranno la tua esperienza di sviluppo JavaScript.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

12 min di lettura
ECMAScript 2024: le novità più importanti per JavaScript

Introduzione

JavaScript continua la sua evoluzione costante, guidata dalle specifiche ECMAScript che ogni anno introducono nuove funzionalità per rendere il linguaggio più potente, espressivo e facile da utilizzare. ECMAScript 2024 (ES2024), la quindicesima edizione dello standard, porta con sé un insieme di aggiunte significative che promettono di migliorare sia la produttività degli sviluppatori che le performance delle applicazioni.

In questo articolo, esploreremo tutte le novità principali introdotte con ECMAScript 2024, dalle operazioni sui set agli helper per gli iteratori, dalle annotazioni di tipo alle nuove utility per array e oggetti. Per ogni funzionalità, forniremo esempi pratici e concisi che ne illustrano l'utilizzo e i vantaggi rispetto alle soluzioni precedenti.

Che tu sia un principiante curioso o uno sviluppatore esperto, questa panoramica ti aiuterà a comprendere come sfruttare al meglio le più recenti evoluzioni di JavaScript per scrivere codice più pulito, manutenibile ed efficiente nei tuoi progetti futuri.

Le principali novità di ECMAScript 2024

Promise.withResolvers()

Una delle novità più attese è Promise.withResolvers(), una funzione di utility che semplifica la gestione delle promise separando la creazione dagli handler di risoluzione.

Come funziona

Promise.withResolvers() restituisce un oggetto contenente una nuova Promise e i suoi metodi resolve e reject, permettendo di accedervi al di fuori del costruttore della Promise.

Esempio

// Approccio tradizionale
let resolveFunction, rejectFunction;
const promise = new Promise((resolve, reject) => {
  resolveFunction = resolve;
  rejectFunction = reject;
});

// Con il nuovo Promise.withResolvers()
const { promise, resolve, reject } = Promise.withResolvers();

// Ora è possibile usare promise, resolve e reject separatamente
setTimeout(() => resolve("Operazione completata"), 1000);

promise.then((value) => console.log(value));

Vantaggi

  • Codice più pulito: Elimina la necessità di variabili temporanee
  • Migliore separazione delle responsabilità: Divide chiaramente la creazione della promise dalla sua risoluzione
  • Ideale per eventi asincroni complessi: Perfetto per gestire eventi che possono verificarsi in qualsiasi momento

Operazioni su Set e WeakSet

ECMAScript 2024 introduce nuovi metodi per lavorare con i Set, aggiungendo operazioni simili a quelle disponibili nella teoria degli insiemi matematici.

Nuovi metodi per Set

  • Set.prototype.intersection(iterable): Restituisce un nuovo Set contenente elementi presenti in entrambi i set
  • Set.prototype.union(iterable): Restituisce un nuovo Set con elementi presenti in almeno uno dei due set
  • Set.prototype.difference(iterable): Restituisce un nuovo Set con elementi presenti nel set originale ma non nell'iterabile fornito
  • Set.prototype.symmetricDifference(iterable): Restituisce un nuovo Set con elementi presenti in uno dei due set, ma non in entrambi
  • Set.prototype.isSubsetOf(iterable): Verifica se il set è un sottoinsieme dell'iterabile fornito
  • Set.prototype.isSupersetOf(iterable): Verifica se il set è un soprainsieme dell'iterabile fornito
  • Set.prototype.isDisjointFrom(iterable): Verifica se il set e l'iterabile non hanno elementi in comune

Esempio

const set1 = new Set([1, 2, 3, 4]);
const set2 = new Set([3, 4, 5, 6]);

// Intersezione: elementi presenti in entrambi i set
const intersection = set1.intersection(set2);
console.log([...intersection]); // [3, 4]

// Unione: tutti gli elementi unici dai due set
const union = set1.union(set2);
console.log([...union]); // [1, 2, 3, 4, 5, 6]

// Differenza: elementi in set1 ma non in set2
const difference = set1.difference(set2);
console.log([...difference]); // [1, 2]

// Differenza simmetrica: elementi in uno dei due set, ma non in entrambi
const symmetricDifference = set1.symmetricDifference(set2);
console.log([...symmetricDifference]); // [1, 2, 5, 6]

// Verifiche di relazione
console.log(new Set([1, 2]).isSubsetOf(set1)); // true
console.log(set1.isSupersetOf(new Set([1, 2]))); // true
console.log(set1.isDisjointFrom(new Set([7, 8]))); // true

Vantaggi

  • Operazioni native: Non è più necessario implementare queste operazioni manualmente
  • Performance migliori: Le implementazioni native sono ottimizzate
  • Codice più espressivo: Le intenzioni del codice sono più chiare

Iterator Helpers

Gli iterator sono una parte fondamentale di JavaScript moderno, e ES2024 introduce una serie di metodi helper che li rendono molto più facili da utilizzare.

Nuovi metodi per Iterator

  • Iterator.prototype.map(callbackFn): Trasforma ogni valore dell'iteratore
  • Iterator.prototype.filter(callbackFn): Filtra i valori dell'iteratore
  • Iterator.prototype.take(limit): Prende solo un numero specifico di valori
  • Iterator.prototype.drop(limit): Salta un numero specifico di valori
  • Iterator.prototype.flatMap(callbackFn): Mappa e appiattisce i risultati
  • Iterator.prototype.reduce(callbackFn, initialValue): Riduce l'iteratore a un singolo valore
  • Iterator.prototype.toArray(): Converte l'iteratore in un array
  • Iterator.prototype.forEach(callbackFn): Esegue una funzione per ogni valore
  • Iterator.prototype.some(callbackFn): Verifica se almeno un valore soddisfa una condizione
  • Iterator.prototype.every(callbackFn): Verifica se tutti i valori soddisfano una condizione
  • Iterator.prototype.find(callbackFn): Trova il primo valore che soddisfa una condizione

Esempio

// Crea un generatore che produce numeri incrementali
function* counter(start = 0, step = 1) {
  let count = start;
  while (true) {
    yield count;
    count += step;
  }
}

// Utilizzo dei nuovi iterator helpers
const result = counter(1)
  .take(10) // Prendi solo i primi 10 numeri
  .filter((n) => n % 2) // Mantieni solo i numeri dispari
  .map((n) => n * 2) // Raddoppia ogni numero
  .toArray(); // Converti in array

console.log(result); // [2, 6, 10, 14, 18]

// Esempio con reduce
const sum = counter(1)
  .take(5)
  .reduce((acc, val) => acc + val, 0);

console.log(sum); // 15 (1 + 2 + 3 + 4 + 5)

Vantaggi

  • Operazioni a catena: Permette di comporre trasformazioni in modo chiaro
  • Lazy evaluation: Le operazioni vengono eseguite solo quando necessario
  • Efficienza della memoria: Ideale per lavorare con sequenze potenzialmente infinite
  • Uniformità: API simile a quella degli array, ma ottimizzata per gli iteratori

Array e metodi di raggruppamento

ECMAScript 2024 introduce nuovi metodi per array che semplificano operazioni comuni di raggruppamento e manipolazione.

Nuovo metodo Object.groupBy

Questo metodo permette di raggruppare gli elementi di un array in base a una chiave determinata da una funzione.

const utenti = [
  { nome: "Alice", dipartimento: "IT" },
  { nome: "Bob", dipartimento: "Vendite" },
  { nome: "Carol", dipartimento: "IT" },
  { nome: "Dave", dipartimento: "Vendite" },
];

// Raggruppamento di oggetti per dipartimento
const perDipartimento = Object.groupBy(utenti, (utente) => utente.dipartimento);

console.log(perDipartimento);
/* Output:
{
  "IT": [
    { nome: "Alice", dipartimento: "IT" },
    { nome: "Carol", dipartimento: "IT" }
  ],
  "Vendite": [
    { nome: "Bob", dipartimento: "Vendite" },
    { nome: "Dave", dipartimento: "Vendite" }
  ]
}
*/

Map.groupBy

Simile a Object.groupBy, ma raggruppa gli elementi in una Map invece che in un oggetto.

const utenti = [
  { nome: "Alice", età: 25 },
  { nome: "Bob", età: 30 },
  { nome: "Carol", età: 25 },
];

// Raggruppamento usando una Map
const perEtà = Map.groupBy(utenti, (utente) => utente.età);

console.log(perEtà);
/* Output: Map {
  25 => [{ nome: "Alice", età: 25 }, { nome: "Carol", età: 25 }],
  30 => [{ nome: "Bob", età: 30 }]
} */

// Vantaggio: possiamo usare qualsiasi tipo di chiave, non solo stringhe
const oggetti = [{ id: {} }, { id: {} }];
const perRiferimento = Map.groupBy(oggetti, (obj) => obj.id);

Vantaggi

  • Leggibilità: Soluzione nativa per un'operazione molto comune
  • Flessibilità: Funziona con qualsiasi array di elementi
  • Performance: Implementazione ottimizzata rispetto a soluzioni manuali
  • Map.groupBy: Supporta chiavi non stringa, a differenza di Object.groupBy

Object Methods

ECMAScript 2024 aggiunge nuovi metodi utili per lavorare con gli oggetti.

Object.hasOwn()

Questo metodo verifica se un oggetto ha una proprietà specifica come proprietà diretta (non ereditata).

const oggetto = { a: 1 };
const prototipo = { b: 2 };
Object.setPrototypeOf(oggetto, prototipo);

// Verifica se una proprietà è diretta (non ereditata)
console.log(Object.hasOwn(oggetto, "a")); // true
console.log(Object.hasOwn(oggetto, "b")); // false
console.log(Object.hasOwn(oggetto, "c")); // false

// Confronto con l'approccio tradizionale
console.log(oggetto.hasOwnProperty("a")); // true
console.log("hasOwnProperty" in oggetto); // true, ma è ereditato

Vantaggi

  • Sicurezza: A differenza di hasOwnProperty, funziona anche con oggetti che potrebbero non avere questo metodo
  • Chiarezza: È più intuitivo come metodo statico di Object
  • Consistenza: Segue lo stile di altri metodi statici come Object.keys

String.prototype.isWellFormed() e toWellFormed()

Questi nuovi metodi aiutano a gestire stringhe Unicode potenzialmente malformate.

// Creiamo una stringa con un carattere surrogato isolato (malformato)
const stringaMalformata = "ciao\uD800mondo";

// Verifica se una stringa è ben formata
console.log(stringaMalformata.isWellFormed()); // false

// Converti in una stringa ben formata
const stringaCorretta = stringaMalformata.toWellFormed();
console.log(stringaCorretta); // "ciao�mondo" (il surrogato è sostituito con U+FFFD)
console.log(stringaCorretta.isWellFormed()); // true

Vantaggi

  • Sicurezza: Aiuta a prevenire problemi con API che richiedono stringhe valide UTF-16
  • Interoperabilità: Semplifica il lavoro con dati provenienti da fonti esterne
  • Debugging: Facilita l'identificazione di problemi di encoding

Atomics.waitAsync

Questa nuova API fornisce una versione asincrona di Atomics.wait, utile per la sincronizzazione tra thread in applicazioni che utilizzano Web Workers.

// Creazione di una memoria condivisa
const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sharedBuffer);

// In un worker principale
Atomics.waitAsync(sharedArray, 0, 0).then(
  (result) => console.log("Notifica ricevuta:", result),
  (error) => console.error("Errore:", error)
);

// In un altro worker
Atomics.store(sharedArray, 0, 1);
Atomics.notify(sharedArray, 0, 1);

Vantaggi

  • Asincronia: A differenza di Atomics.wait, non blocca il thread principale
  • Promise-based: Si integra meglio con altre API asincrone moderne
  • Performance: Ideale per applicazioni complesse con operazioni di sincronizzazione

Type Annotations (Proposta in fase 3)

Una delle proposte più interessanti in fase avanzata di approvazione riguarda le annotazioni di tipo direttamente in JavaScript, simili a TypeScript ma senza la fase di compilazione.

Nota: Questa funzionalità è ancora in Stage 3 al momento della stesura dell'articolo e potrebbe subire modifiche prima della finalizzazione.

Esempio

// Dichiarazione di tipi
type User = {
  id: number,
  name: string,
  isActive: boolean,
};

// Funzione con annotazioni di tipo
function getUser(id: number): User {
  // Il corpo della funzione rimane JavaScript standard
  return {
    id,
    name: "Utente " + id,
    isActive: true,
  };
}

// Le annotazioni di tipo vengono ignorate a runtime
const user = getUser(42);
console.log(user); // { id: 42, name: "Utente 42", isActive: true }

Come funziona

Le annotazioni di tipo in JavaScript sono progettate per essere completamente ignorate a runtime. Sono destinate esclusivamente agli strumenti di analisi statica e agli IDE, che possono fornire suggerimenti, rilevare errori e migliorare l'esperienza di sviluppo.

Vantaggi potenziali

  • Nessuna compilazione: A differenza di TypeScript, non richiede un passaggio di transpilazione
  • Adozione graduale: Può essere introdotto progressivamente in base alle esigenze
  • Compatibilità: Le annotazioni sono parte della sintassi ufficiale e non causano errori nei runtime JavaScript
  • Tooling migliorato: Gli IDE possono offrire suggerimenti e controlli tipo anche senza TypeScript

Stage 3: Temporal API

Un'altra proposta in fase avanzata è la Temporal API, progettata per sostituire la problematica API Date con un sistema più robusto e intuitivo per la gestione di date e orari.

// Creazione di una data
const oggi = Temporal.Now.plainDateISO();
console.log(oggi.toString()); // '2025-03-19'

// Aritmetica delle date (immutabile)
const tra10Giorni = oggi.add({ days: 10 });
console.log(tra10Giorni.toString()); // '2025-03-29'

// Confronto date
console.log(tra10Giorni > oggi); // true

// Lavorare con fusi orari
const oraPacific = Temporal.Now.zonedDateTimeISO("America/Los_Angeles");
const oraRoma = Temporal.Now.zonedDateTimeISO("Europe/Rome");
console.log(oraPacific.toString());
console.log(oraRoma.toString());

// Calcolo della durata tra date
const durata = oraRoma.since(oraPacific);
console.log(durata.hours); // Differenza in ore

Vantaggi

  • Immutabilità: Tutte le operazioni restituiscono nuovi oggetti invece di modificare quelli esistenti
  • API intuitiva: Metodi chiari e specifici per diverse operazioni
  • Supporto robusto per fusi orari: Gestione nativa dei fusi orari e delle loro complessità
  • Precisione: Supporto per timestamp ad alta precisione e calcoli accurati

Altre proposte in fase avanzata

Decoratori (Stage 3)

I decoratori forniscono un modo per modificare classi e membri di classe in modo dichiarativo.

// Definizione di un decoratore
function logged(target, context) {
  const { kind, name } = context;

  if (kind === "method") {
    const original = target;

    function replacementMethod(...args) {
      console.log(`Chiamata al metodo ${name} con argomenti:`, args);
      const result = original.call(this, ...args);
      console.log(`Il metodo ${name} ha restituito:`, result);
      return result;
    }

    return replacementMethod;
  }
}

// Utilizzo del decoratore
class Esempio {
  @logged
  moltiplicazione(a, b) {
    return a * b;
  }
}

const esempio = new Esempio();
esempio.moltiplicazione(2, 3);
// Output:
// Chiamata al metodo moltiplicazione con argomenti: [2, 3]
// Il metodo moltiplicazione ha restituito: 6

Import Assertions (Stage 3)

Questa funzionalità permette di specificare il formato di un modulo durante l'importazione.

// Importazione con specifica del tipo
import data from "./data.json" assert { type: "json" };

// Importazione dinamica con assertions
const translations = await import("./translations.json", {
  assert: { type: "json" },
});

Supporto nei browser e ambienti

La disponibilità delle nuove funzionalità di ECMAScript 2024 varia tra i diversi ambienti JavaScript:

Browser

  • Chrome: Supporto parziale per le funzionalità stabili nelle versioni più recenti
  • Firefox: Implementazione in corso per le funzionalità principali
  • Safari: Supporto variabile, tipicamente più lento nell'adozione
  • Edge: Segue Chrome grazie al motore Chromium condiviso

Node.js

Node.js 22 e versioni successive dovrebbero includere la maggior parte delle funzionalità di ES2024.

Transpilatori

  • Babel: Offre plugin per utilizzare le funzionalità ES2024 anche in ambienti che non le supportano nativamente
  • TypeScript: Supporta gradualmente le nuove funzionalità JavaScript nelle versioni più recenti

Come verificare il supporto

Puoi verificare il supporto specifico per ogni funzionalità su Can I use o Compatibility Table.

Conclusione

ECMAScript 2024 rappresenta un altro passo significativo nell'evoluzione di JavaScript, introducendo funzionalità che rendono il linguaggio più espressivo, potente e facile da utilizzare. Le nuove aggiunte come Promise.withResolvers(), le operazioni sui Set, gli Iterator Helpers e i metodi di raggruppamento per array rispondono a esigenze concrete degli sviluppatori, semplificando pattern comuni e migliorando la leggibilità del codice.

Particolarmente interessanti sono anche le proposte in fase avanzata come le annotazioni di tipo e la Temporal API, che potrebbero trasformare radicalmente il modo in cui scriviamo JavaScript nei prossimi anni.

Per sfruttare al meglio queste novità, è consigliabile:

  1. Iniziare a sperimentare: Utilizzare le nuove funzionalità in progetti personali o non critici
  2. Rimanere aggiornati: Seguire l'evoluzione delle proposte ancora in fase di standardizzazione
  3. Utilizzare transpilatori: Adottare strumenti come Babel per usare le funzionalità anche in ambienti che non le supportano nativamente
  4. Aggiornare le conoscenze: Dedicare tempo a comprendere le nuove API e il loro impatto sulle best practice

Con ogni nuova versione, JavaScript continua a dimostrare la sua capacità di evolversi mantenendo la compatibilità con il passato, consolidando il suo ruolo di linguaggio fondamentale per lo sviluppo web moderno.

Risorse utili