Rattraper les dix dernières années de JavaScript : d’ES2015 à l’ère de l’IA, qu’est-ce qui a vraiment changé ?

Infographic showing JavaScript evolution from ES6 in 2015 to 2026 innovations
Écouter cet article

Onze éditions d’ECMAScript, quelques révolutions, beaucoup de petites réparations et suffisamment de nouveaux outils pour éviter d’installer un paquet npm simplement afin de retourner un tableau.

Si vous avez appris JavaScript à l’époque où var, les fonctions anonymes et les concaténations de chaînes régnaient sans partage, le langage actuel peut donner l’impression d’avoir été remplacé pendant votre pause-café. Bonne nouvelle : JavaScript est toujours JavaScript. Il convertit encore des types avec l’enthousiasme d’un stagiaire qui veut aider, this continue de punir les personnes trop confiantes et [] + [] produit toujours une chaîne vide — parce qu’il faut bien préserver quelques traditions familiales.

Mais le langage de 2026 n’est plus celui de 2015. Il est plus structuré, plus lisible, mieux équipé pour l’asynchronisme, les modules, les collections, l’immuabilité et le traitement de données. Surtout, l’écosystème a changé autour de lui : navigateurs modernes, Node.js, TypeScript, outils de compilation, tests, frameworks, runtimes alternatifs et assistants d’intelligence artificielle.

Le piège consiste à tout mélanger. Une nouvelle méthode de tableau n’est pas un nouveau framework. fetch() n’est pas défini par ECMAScript. TypeScript n’est pas JavaScript avec un abonnement premium. Et une IA capable de générer cinquante fichiers en trente secondes n’a pas rendu ces cinquante fichiers corrects.

Faisons donc le tri.

JavaScript, ECMAScript, TC39 : qui fait quoi dans cette réunion ?

JavaScript est le nom couramment utilisé pour le langage. ECMAScript est sa spécification standardisée, c’est-à-dire le document qui décrit précisément sa syntaxe et son comportement. Le TC39 est le comité technique qui fait évoluer cette spécification.

En juin 2015, ECMAScript 2015, également appelé ES6, est devenu la sixième édition de la norme ECMA-262. Depuis, une édition est publiée chaque année. Au 26 août 2026, la version officiellement publiée est ECMAScript 2026, dix-septième édition de la norme.

ES6 = ECMAScript 2015 = 6e édition
ES2025 = ECMAScript 2025 = 16e édition
ES2026 = ECMAScript 2026 = 17e édition

La spécification officielle confirme l’édition 2026 et conserve les archives des éditions précédentes : ECMA-262 — Ecma International.

Depuis ES2015, le langage n’attend plus plusieurs années avant de livrer une énorme cargaison de nouveautés. Les propositions passent par plusieurs étapes au TC39. Lorsqu’elles atteignent le stade 4, elles sont considérées comme terminées et destinées à intégrer la spécification. Le registre officiel est disponible dans la liste des propositions terminées du TC39.

Cette cadence annuelle a produit un effet curieux : les numéros changent tous les ans, mais toutes les éditions ne bouleversent pas votre façon de programmer. ES2015 a déplacé les murs porteurs. Certaines éditions suivantes ont surtout installé de meilleures poignées de porte. Les poignées sont utiles, mais personne n’organise une pendaison de crémaillère pour String.prototype.trimStart().

2015 : l’année où JavaScript a décidé de devenir fréquentable

ES2015 est la grande rupture. La spécification elle-même présente les modules, les classes, la portée lexicale par bloc, les itérateurs, les générateurs, les promesses et la déstructuration parmi ses évolutions majeures. Son ambition était claire : mieux prendre en charge les grandes applications et la création de bibliothèques. La spécification ES2015 reste la source de référence.

let et const remplacent progressivement var

var possède une portée de fonction et accepte plusieurs comportements historiques surprenants. let et const ont une portée de bloc, c’est-à-dire qu’ils n’existent que dans la zone délimitée par les accolades où ils sont déclarés.

if (true) {
  // Cette variable reste dans le bloc.
  let message = 'Bonjour';

  // Cette référence ne pourra pas être réaffectée.
  const langage = 'JavaScript';
}

// ReferenceError : message n'existe pas ici.
// console.log(message);

La règle moderne est simple : utilisez const par défaut, puis let lorsque la variable doit recevoir une nouvelle valeur. Gardez var pour la maintenance de code ancien et les soirées consacrées à raconter des histoires qui commencent par « à l’époque, Internet Explorer… ».

Attention : const interdit la réaffectation de la variable, pas la modification interne de l’objet.

const utilisateur = {
  nom: 'Alice'
};

// Autorisé : l'objet reste le même, sa propriété change.
utilisateur.nom = 'Bob';

// Interdit : la variable ne peut pas viser un autre objet.
// utilisateur = {};

Les fonctions fléchées raccourcissent la syntaxe — et changent this

// Ancienne écriture.
const multiplierAvant = function (a, b) {
  return a * b;
};

// Fonction fléchée avec retour implicite.
const multiplier = (a, b) => a * b;

Le gain n’est pas uniquement esthétique. Une fonction fléchée n’a pas son propre this : elle récupère celui du contexte où elle a été créée. On parle de this lexical.

class Chronometre {
  secondes = 0;

  demarrer() {
    setInterval(() => {
      // La flèche conserve le `this` de demarrer().
      this.secondes += 1;
    }, 1000);
  }
}

Une fonction fléchée n’est toutefois pas un remplacement universel. Elle ne peut pas servir de constructeur avec new et convient mal comme méthode lorsqu’on veut que this soit fourni au moment de l’appel. Oui, même la syntaxe courte a une notice. Non, personne ne la lit avant le premier bug.

Les chaînes de gabarit mettent fin à la plomberie de +

Les template literals, ou chaînes de gabarit, utilisent des accents graves et permettent l’interpolation avec ${...}.

const nom = 'Alice';
const age = 32;

// Avant : une petite clôture de signes +.
const ancienTexte = 'Je m’appelle ' + nom + ' et j’ai ' + age + ' ans.';

// Après : la phrase ressemble enfin à une phrase.
const texte = `Je m’appelle ${nom} et j’ai ${age} ans.`;

Elles acceptent aussi plusieurs lignes et peuvent être utilisées avec des fonctions de balisage, qui reçoivent séparément les fragments de texte et les valeurs interpolées.

Déstructuration, paramètres par défaut, rest et spread

La déstructuration extrait des valeurs d’un objet ou d’un tableau. Le paramètre rest rassemble plusieurs valeurs. L’opérateur spread déploie les valeurs d’un itérable, par exemple un tableau.

const profil = {
  nom: 'Alice',
  role: 'développeuse'
};

// Déstructuration et valeur par défaut.
const { nom, role, actif = true } = profil;

// Rest : les arguments deviennent un tableau.
function additionner(...nombres) {
  return nombres.reduce((total, nombre) => total + nombre, 0);
}

// Spread : les éléments sont déployés dans un nouveau tableau.
const nombres = [1, 2, 3];
const copieEtSuite = [...nombres, 4, 5];

En ES2015, le spread concernait les appels de fonctions et les éléments de tableaux. La forme { ...objet }, aujourd’hui omniprésente, a été standardisée en ES2018. Internet l’a depuis utilisée avec tant d’ardeur qu’on pourrait croire qu’un objet ne peut plus être créé sans être cloné trois fois.

Les objets deviennent moins répétitifs

const nom = 'Alice';
const role = 'développeuse';
const cle = 'actif';

const utilisateur = {
  // Propriétés raccourcies.
  nom,
  role,

  // Nom de propriété calculé.
  [cle]: true,

  // Méthode raccourcie.
  saluer() {
    return `Bonjour, je suis ${this.nom}`;
  }
};

Les classes arrivent, mais les prototypes ne partent pas

class Personne {
  constructor(nom) {
    this.nom = nom;
  }

  sePresenter() {
    return `Je m’appelle ${this.nom}`;
  }
}

class Developpeur extends Personne {
  constructor(nom, langage) {
    super(nom);
    this.langage = langage;
  }
}

Les classes offrent une syntaxe plus lisible pour construire des objets et organiser l’héritage. Elles reposent toujours sur le système de prototypes, le mécanisme par lequel les objets JavaScript héritent d’autres objets. ES2015 n’a donc pas transformé JavaScript en Java. Il lui a simplement prêté une veste qui ressemble à celle de Java, ce qui a suffi pour déclencher dix ans de débats parfaitement sereins sur l’architecture logicielle.

Les modules donnent enfin une structure native aux applications

// calculs.js
export function additionner(a, b) {
  return a + b;
}

export const PI = 3.14159;

// application.js
import { additionner, PI } from './calculs.js';

console.log(additionner(10, 20));
console.log(PI);

Les modules ECMAScript, souvent abrégés ESM, définissent nativement import et export. Ils permettent l’analyse statique des dépendances : un outil peut déterminer les imports avant l’exécution du programme. Les modules appliquent automatiquement le mode strict.

Dans un navigateur :

<!-- Le navigateur charge application.js comme module ECMAScript. -->
<script type="module" src="application.js"></script>

Les modules ont progressivement offert un langage commun aux navigateurs, aux outils de compilation et aux environnements serveur. Le voyage a été moins direct qu’un import ne le laisse penser, notamment à cause de l’héritage de CommonJS dans Node.js. Mais en 2026, écrire du code neuf en modules ESM est devenu un choix normal, pas une expérience sociale.

Les promesses civilisent l’asynchronisme

Une promesse représente le résultat futur d’une opération. Elle peut être en attente, réussie ou échouée.

function chargerProfil() {
  return fetch('/api/profil')
    .then((reponse) => {
      if (!reponse.ok) {
        throw new Error(`HTTP ${reponse.status}`);
      }

      return reponse.json();
    });
}

chargerProfil()
  .then((profil) => console.log(profil))
  .catch((erreur) => console.error(erreur));

Les promesses ont fourni une base standard au code asynchrone. Elles ont surtout préparé le terrain pour async et await, arrivés deux ans plus tard. Avant cela, les rappels imbriqués formaient parfois une pyramide assez ambitieuse pour obtenir un permis de construire.

Itérateurs, générateurs, for...of, Map, Set, Symbol, Proxy et Reflect

ES2015 ajoute aussi les mécanismes qui permettront de mieux parcourir, produire et contrôler des données.

// Set stocke des valeurs sans doublons.
const etiquettes = new Set(['js', 'web', 'js']);

// Map associe des clés à des valeurs.
const scores = new Map([
  ['Alice', 18],
  ['Bob', 15]
]);

// for...of parcourt les valeurs d'un itérable.
for (const etiquette of etiquettes) {
  console.log(etiquette);
}

// Un générateur produit des valeurs à la demande.
function* compteur() {
  yield 1;
  yield 2;
  yield 3;
}

Un itérable est une valeur que JavaScript sait parcourir selon un protocole standard. Un générateur est une fonction qui peut se mettre en pause avec yield, puis reprendre. Symbol fournit des valeurs uniques et sert notamment à définir des protocoles internes comme Symbol.iterator. Proxy intercepte des opérations sur un objet ; Reflect regroupe des opérations standard sur les objets.

Vous n’utiliserez probablement pas Proxy chaque mardi. C’est plutôt rassurant : on n’utilise pas non plus un chalumeau pour tartiner du beurre, même s’il est techniquement disponible.

De 2016 à 2019 : consolider le chantier

Après l’explosion d’ES2015, les éditions suivantes deviennent plus petites et plus régulières.

ÉditionAjouts à retenirCe que cela change réellement
ES2016Array.prototype.includes(), opérateur **Des tests d’appartenance plus lisibles et une puissance mathématique sans détour par Math.pow()
ES2017async/await, Object.values(), Object.entries(), padStart(), padEnd(), mémoire partagée et AtomicsL’asynchronisme devient lisible comme du code séquentiel
ES2018itération asynchrone, Promise.prototype.finally(), rest/spread pour objets, fortes améliorations des expressions régulièresLe traitement de flux asynchrones et la copie d’objets deviennent plus naturels
ES2019flat(), flatMap(), Object.fromEntries(), trimStart(), trimEnd(), paramètre de catch optionnelMoins de petites fonctions utilitaires écrites à la main

La vedette de cette période est évidemment async/await.

async function chargerProfil() {
  const reponse = await fetch('/api/profil');

  if (!reponse.ok) {
    throw new Error(`HTTP ${reponse.status}`);
  }

  return reponse.json();
}

try {
  const profil = await chargerProfil();
  console.log(profil);
} catch (erreur) {
  console.error(erreur);
}

Une fonction async renvoie toujours une promesse. await suspend cette fonction jusqu’au règlement de la promesse, sans bloquer tout le programme. Le code paraît séquentiel, mais l’opération reste asynchrone. C’est plus lisible ; ce n’est pas magique. Si vous lancez trois appels indépendants l’un après l’autre avec trois await, ils seront toujours lents l’un après l’autre, mais avec une syntaxe très élégante.

// Les trois opérations indépendantes démarrent ensemble.
const [profil, messages, alertes] = await Promise.all([
  chargerProfil(),
  chargerMessages(),
  chargerAlertes()
]);

ES2018 ajoute aussi l’itération asynchrone :

// Chaque élément peut arriver plus tard.
for await (const evenement of fluxEvenements) {
  console.log(evenement);
}

Et ES2019 complète les transformations de collections :

const groupes = [[1, 2], [3, 4]];

// Aplatit un niveau de tableaux imbriqués.
const valeurs = groupes.flat();

const entrees = [
  ['nom', 'Alice'],
  ['actif', true]
];

// Réalise l'opération inverse d'Object.entries().
const profil = Object.fromEntries(entrees);

2020 : l’année où JavaScript supprime plusieurs irritants quotidiens

ES2020 rassemble des ajouts qui ont rapidement modifié le style du code moderne : le chaînage optionnel ?., la coalescence des valeurs nulles ??, BigInt, Promise.allSettled(), globalThis, les imports dynamiques et String.prototype.matchAll().

?. : arrêter d’escalader un objet avec un casque

// Renvoie undefined si profil ou adresse est null ou undefined.
const ville = profil?.adresse?.ville;

// Appelle la méthode seulement si elle existe.
plugin.initialiser?.();

Avant, il fallait enchaîner des tests ou confier la mission à une bibliothèque. ?. s’arrête uniquement devant null ou undefined, pas devant toutes les valeurs considérées comme fausses.

?? : respecter enfin 0, false et la chaîne vide

const configuration = {
  volume: 0,
  notifications: false
};

// Conserve 0, car il ne s'agit ni de null ni de undefined.
const volume = configuration.volume ?? 50;

// Conserve false pour la même raison.
const notifications = configuration.notifications ?? true;

Avec ||, les valeurs 0, false et '' auraient été remplacées. L’opérateur ?? ne choisit la valeur de secours que pour null ou undefined. Une différence minuscule, jusqu’au jour où votre application remet le volume à 50 après que l’utilisateur a clairement demandé le silence.

BigInt, les promesses regroupées et les imports à la demande

// Le suffixe n crée un entier de précision arbitraire.
const tresGrandEntier = 9_007_199_254_740_993n;

// Attend toutes les promesses sans s'arrêter au premier échec.
const resultats = await Promise.allSettled([
  chargerProfil(),
  chargerMessages()
]);

// Charge un module uniquement lorsque nécessaire.
const moduleGraphique = await import('./graphique.js');
moduleGraphique.afficher();

BigInt représente des entiers au-delà de la plage exacte du type Number. Il ne faut pas mélanger directement BigInt et Number dans les calculs. JavaScript refuse, probablement parce qu’il connaît déjà la réputation de ses conversions implicites.

2021 et 2022 : moins de cérémonial, de vraies données privées

ES2021 apporte notamment replaceAll(), Promise.any(), les séparateurs numériques, les opérateurs d’affectation logique et les références faibles.

// Le séparateur améliore la lecture sans changer la valeur.
const budget = 1_000_000;

// Remplace toutes les occurrences littérales.
const slug = 'java script moderne'.replaceAll(' ', '-');

// Affecte seulement si cache vaut null ou undefined.
cache ??= creerCache();

Promise.any() renvoie le premier résultat réussi et échoue seulement si toutes les promesses échouent. Les WeakRef et FinalizationRegistry concernent des usages avancés liés au ramasse-miettes, le mécanisme qui libère automatiquement la mémoire. Si vous n’avez pas un besoin précis, ne les utilisez pas « pour optimiser ». L’optimisation sans mesure est souvent une manière coûteuse de déplacer un problème imaginaire.

ES2022 modernise fortement les classes : champs publics, champs privés avec #, méthodes privées, champs statiques et blocs statiques. Il ajoute aussi await au niveau supérieur des modules, .at(), Object.hasOwn(), la cause des erreurs et les indices de correspondance des expressions régulières.

class Compte {
  // Champ public.
  devise = 'EUR';

  // Champ réellement privé au niveau du langage.
  #solde = 0;

  deposer(montant) {
    this.#solde += montant;
  }

  consulter() {
    return this.#solde;
  }
}

const valeurs = ['a', 'b', 'c'];

// Accès au dernier élément sans valeurs[valeurs.length - 1].
console.log(valeurs.at(-1));

Le mot « privé » n’est plus une convention reposant sur un _nomDePropriete et la politesse des collègues. Le moteur interdit réellement l’accès externe au champ #solde.

2023 et 2024 : copier sans muter, regrouper sans bricoler

ES2023 introduit findLast() et findLastIndex(), ainsi que quatre méthodes de tableau qui renvoient une copie au lieu de modifier le tableau original : toReversed(), toSorted(), toSpliced() et with().

const nombres = [3, 1, 2];

// sort() modifierait nombres ; toSorted() renvoie une copie triée.
const tries = nombres.toSorted((a, b) => a - b);

// Remplace l'élément d'index 1 dans une nouvelle copie.
const corriges = nombres.with(1, 10);

console.log(nombres);  // [3, 1, 2]
console.log(tries);    // [1, 2, 3]
console.log(corriges); // [3, 10, 2]

Ces méthodes facilitent une programmation immuable, c’est-à-dire un style où l’on crée une nouvelle valeur plutôt que de modifier l’ancienne. Ce principe est utile pour raisonner sur les états d’interface et les traitements de données. Il ne transforme pas chaque copie en vertu morale ; copier des structures gigantesques sans réfléchir reste une excellente façon de chauffer une pièce.

ES2024 ajoute notamment Object.groupBy(), Map.groupBy(), Promise.withResolvers(), les chaînes Unicode bien formées, des ArrayBuffer redimensionnables ou transférables, Atomics.waitAsync() et le drapeau v des expressions régulières.

const produits = [
  { nom: 'Clavier', categorie: 'matériel' },
  { nom: 'Éditeur', categorie: 'logiciel' },
  { nom: 'Souris', categorie: 'matériel' }
];

// Regroupe les objets selon la valeur renvoyée.
const parCategorie = Object.groupBy(
  produits,
  (produit) => produit.categorie
);

Promise.withResolvers() évite une petite cérémonie lors de la création d’une promesse dont les fonctions de résolution doivent être conservées à l’extérieur.

const { promise, resolve, reject } = Promise.withResolvers();

// Ces fonctions peuvent être transmises à un autre composant.
connecterSourceExterne({ resolve, reject });

const resultat = await promise;

2025 : les collections et les itérateurs passent enfin à l’âge adulte

ES2025 ajoute plusieurs outils qui évitent des boucles et des dépendances utilitaires : méthodes d’ensembles, aides pour les itérateurs, attributs d’import, modules JSON, modificateurs d’expressions régulières, groupes de capture nommés dupliqués dans certaines branches, RegExp.escape(), Promise.try() et la prise en charge des nombres flottants 16 bits dans les tableaux typés.

Les nouvelles méthodes de Set rendent directement disponibles les opérations classiques sur les ensembles.

const equipeFrontend = new Set(['Alice', 'Bob']);
const equipeBackend = new Set(['Bob', 'Chloé']);

// Union : tous les membres sans doublons.
const toutLeMonde = equipeFrontend.union(equipeBackend);

// Intersection : les membres présents dans les deux ensembles.
const polyvalents = equipeFrontend.intersection(equipeBackend);

// Différence : les membres uniquement dans la première équipe.
const uniquementFrontend = equipeFrontend.difference(equipeBackend);

Les aides d’itérateurs permettent d’enchaîner des opérations de façon paresseuse. « Paresseuse » signifie que les valeurs sont calculées lorsqu’elles sont demandées, pas toutes à l’avance.

const resultat = Iterator
  .from([1, 2, 3, 4, 5, 6])
  .filter((nombre) => nombre % 2 === 0)
  .map((nombre) => nombre * 10)
  .take(2)
  .toArray();

console.log(resultat); // [20, 40]

Les attributs d’import précisent la nature d’une ressource importée.

// La syntaxe signale explicitement un module JSON.
import configuration from './configuration.json' with { type: 'json' };

La présence d’une fonctionnalité dans la norme ne garantit pas qu’elle soit immédiatement disponible dans tous vos environnements cibles. Il faut vérifier les versions des navigateurs et runtimes réellement pris en charge. « Ça marche sur ma machine » n’est pas une matrice de compatibilité, même lorsque la machine possède beaucoup de mémoire vive et des autocollants rassurants.

2026 : des outils précis plutôt qu’une nouvelle révolution

ECMAScript 2026, dix-septième édition publiée en juin 2026, poursuit la stratégie des améliorations ciblées. Parmi les ajouts figurent l’insertion conditionnelle dans Map et WeakMap, l’accès au texte source lors de JSON.parse(), Iterator.concat(), les conversions Base64 et hexadécimales de Uint8Array, Math.sumPrecise(), Error.isError() et Array.fromAsync().

Map.getOrInsert() : le cache sans danse rituelle

const utilisateurs = new Map();

// Renvoie la valeur existante ou insère la valeur par défaut.
const profil = utilisateurs.getOrInsert('alice', {
  nom: 'Alice',
  connexions: 0
});

// Calcule la valeur uniquement lorsqu'elle est absente.
const preferences = utilisateurs.getOrInsertComputed(
  'preferences:alice',
  () => chargerPreferencesParDefaut()
);

Les variantes existent également sur WeakMap. Le mécanisme est souvent appelé upsert, contraction de update et insert, même si les noms standardisés utilisent getOrInsert.

Array.fromAsync() et Iterator.concat()

// Transforme un itérable asynchrone en tableau.
const evenements = await Array.fromAsync(fluxEvenements());

// Concatène plusieurs itérables sans construire d'abord un grand tableau.
const nombres = Iterator
  .concat([1, 2], new Set([3, 4]))
  .toArray();

Array.fromAsync() est l’équivalent asynchrone de Array.from(). Iterator.concat() produit un itérateur parcourant successivement plusieurs itérables.

Base64 et hexadécimal directement sur Uint8Array

// Convertit une chaîne Base64 en octets.
const octets = Uint8Array.fromBase64('SGVsbG8=');

// Reconvertit les octets en représentations textuelles.
const base64 = octets.toBase64();
const hexadecimal = octets.toHex();

Un Uint8Array est un tableau typé contenant des entiers non signés sur 8 bits, donc des octets. Ces méthodes évitent des détours historiques par des API conçues autour de chaînes binaires.

Des additions plus fiables et des erreurs mieux reconnues

// Additionne un itérable avec une meilleure précision numérique.
const total = Math.sumPrecise([0.1, 0.2, 0.3]);

// Reconnaît un véritable objet Error, y compris entre différents contextes.
if (Error.isError(erreur)) {
  console.error(erreur.message);
}

Math.sumPrecise() réduit les erreurs accumulées lors de l’addition de nombres flottants. Il ne transforme toutefois pas les nombres binaires en monnaie exacte. Pour des calculs financiers, une représentation adaptée — par exemple des montants en unités entières — reste nécessaire.

JSON.parse() conserve le texte d’origine lorsque c’est nécessaire

const json = '{"identifiant":9007199254740993}';

const donnees = JSON.parse(json, (cle, valeur, contexte) => {
  if (cle === 'identifiant') {
    // Le texte source évite de réutiliser un Number déjà arrondi.
    return BigInt(contexte.source);
  }

  return valeur;
});

Le troisième argument du reviver — la fonction qui transforme les valeurs pendant l’analyse du JSON — peut exposer le texte source d’une valeur primitive non modifiée. C’est utile lorsque la conversion automatique en Number aurait perdu de la précision.

Le détail essentiel n’est pas de mémoriser chaque méthode d’ES2026. Il est de comprendre la direction : le langage intègre progressivement des opérations courantes qui exigeaient auparavant du code auxiliaire, une bibliothèque ou un arrangement artisanal vaguement documenté dans un commentaire daté de 2018.

Ce qui a vraiment changé dans la manière d’écrire du JavaScript

La chronologie annuelle est utile pour les quiz. Pour travailler, cinq transformations comptent davantage.

1. Le code est structuré en modules

Les dépendances ne reposent plus nécessairement sur des variables globales, l’ordre mystérieux de dix balises <script> ou un chargeur propre à chaque projet. import et export ont créé une base commune au navigateur, au serveur et aux outils.

Conséquence pratique : on raisonne davantage en interfaces de modules, en dépendances explicites et en chargement à la demande.

2. L’asynchronisme possède un vocabulaire cohérent

Promesses, fonctions async, await, itérateurs asynchrones et méthodes de combinaison forment désormais un ensemble. Le problème n’a pas disparu : concurrence, annulation, délais, erreurs partielles et ordre d’exécution demandent toujours de la réflexion. La syntaxe permet simplement de réfléchir au problème au lieu de compter les parenthèses fermantes.

3. Le langage favorise les transformations déclaratives

Déstructuration, spread, map(), filter(), flatMap(), Object.fromEntries(), groupBy(), méthodes de Set, aides d’itérateurs et méthodes de copie permettent de décrire davantage ce que l’on veut obtenir.

Cela ne signifie pas qu’une chaîne de quatorze méthodes est automatiquement lisible. À partir d’un certain point, le code cesse d’être déclaratif et devient un rébus avec coloration syntaxique.

4. Les valeurs absentes sont mieux traitées

Le couple ?. et ??, Object.hasOwn(), les causes d’erreurs et les résultats détaillés des promesses réduisent plusieurs ambiguïtés historiques. Le langage distingue mieux « absent », « faux », « vide » et « en échec ».

5. L’immuabilité est plus simple, mais pas obligatoire

Les méthodes toSorted(), toReversed(), toSpliced() et with() évitent de modifier accidentellement les tableaux. Le spread facilite les copies superficielles.

Le mot superficiel est crucial :

const original = {
  profil: {
    nom: 'Alice'
  }
};

// Le premier niveau est copié, mais profil reste partagé.
const copie = { ...original };

copie.profil.nom = 'Bob';

console.log(original.profil.nom); // "Bob"

Le spread ne réalise pas une copie profonde. Il ne lit pas vos intentions et n’a jamais promis de sauver votre état Redux à votre place.

Ce qui n’est pas JavaScript, même si tout le monde l’appelle JavaScript

Une partie du sentiment d’accélération vient de technologies situées autour du langage.

ÉlémentCe que c’est réellement
DOMAPI du navigateur pour manipuler les documents HTML
fetch()API fournie par l’environnement d’exécution, pas par ECMA-262
Boucle d’événementsMécanisme défini par l’environnement hôte, notamment la plateforme Web
Node.js, Deno, BunEnvironnements capables d’exécuter JavaScript hors du navigateur
TypeScriptLangage ajoutant notamment des types statiques, puis transformé en JavaScript
React, Vue, Angular, SvelteOutils ou frameworks d’interface, pas des éditions d’ECMAScript
npmRegistre et écosystème de paquets
Babel, esbuild, SWC, ViteOutils de transformation, compilation ou construction
WebAssemblyFormat d’instructions distinct, conçu pour cohabiter avec JavaScript sur le Web

Cette distinction est plus qu’une querelle de vocabulaire. Elle aide à chercher la bonne documentation et à diagnostiquer les problèmes. Si fetch() se comporte différemment entre deux runtimes, ouvrir ECMA-262 avec détermination ne résoudra rien. Ce sera très professionnel, simplement inutile.

Le standard HTML du WHATWG décrit notamment le modèle de traitement et la boucle d’événements de la plateforme Web : HTML — Web application APIs. ECMAScript décrit le langage lui-même.

L’écosystème : la seconde révolution, moins proprement numérotée

Entre 2015 et 2026, le changement le plus visible ne vient pas toujours de la norme.

TypeScript a déplacé la détection de nombreuses erreurs

Le typage statique analyse le code avant son exécution afin de vérifier la cohérence des valeurs et des interfaces. TypeScript n’empêche ni les mauvaises exigences, ni les erreurs métier, ni les développeurs de forcer un type avec une assurance spectaculaire. Il rend toutefois explicites de nombreux contrats qui vivaient autrefois dans des commentaires, des tests ou la mémoire d’une personne actuellement en congé.

Les outils de construction sont devenus plus rapides et plus intégrés

Compilation, rechargement à chaud, découpage du code, tests, formatage et analyse statique sont désormais attendus dans de nombreux projets. Le bundling consiste à regrouper et optimiser les modules pour la livraison. La transpilation transforme une syntaxe récente en syntaxe compatible avec des environnements plus anciens.

Ces outils restent des moyens, pas une architecture. Un démarrage en 40 millisecondes ne compense pas un modèle de données incohérent ; il permet seulement de découvrir l’incohérence plus rapidement.

Le navigateur moderne exécute davantage de JavaScript récent directement

La nécessité de transformer chaque nouveauté a diminué pour les projets ciblant des environnements récents. Mais le choix dépend toujours du public, des navigateurs, des versions de runtime et des contraintes du produit. La bonne question n’est pas « est-ce que JavaScript supporte cette syntaxe ? », mais « est-ce que tous mes environnements cibles la supportent, et quelle stratégie de repli ai-je choisie ? ».

Le serveur et le client partagent plus facilement des conventions

Les modules ESM, les API Web adoptées par plusieurs runtimes et les outils communs réduisent certaines différences entre navigateur et serveur. Elles ne les effacent pas. Un système de fichiers, le DOM et les politiques de sécurité du navigateur ne deviennent pas interchangeables parce que les deux côtés comprennent async.

Puis l’IA est arrivée : le langage n’a pas changé, le goulot d’étranglement oui

Les assistants fondés sur des modèles de langage — des systèmes entraînés à produire du texte et du code à partir d’instructions — ont changé le coût de génération du logiciel. Ils peuvent produire rapidement une fonction, un test, une migration, une documentation, un exemple d’API ou une première version de composant.

Avant, le temps était souvent dépensé à écrire le premier jet. Avec l’IA, il se déplace vers la formulation du besoin, la vérification, l’intégration et la maintenance.

Cela produit quatre changements concrets.

1. La syntaxe devient moins chère, le contexte devient plus précieux

Une IA peut générer une fonction utilisant ?., ??, Promise.allSettled() ou les aides d’itérateurs sans que vous mémorisiez l’année de standardisation. Elle ne connaît pas automatiquement vos contraintes métier, votre matrice de compatibilité, vos conventions, vos menaces de sécurité ou la raison pour laquelle un bout de code apparemment absurde existe depuis six ans.

Le développeur apporte donc moins de valeur en récitant la liste des méthodes et davantage en définissant le problème, les invariants, les limites et les critères de réussite.

Un invariant est une condition qui doit toujours rester vraie. Exemple : « le solde ne peut jamais devenir négatif » est plus utile à une IA que « crée-moi une classe moderne et élégante », formulation qui appelle surtout une généreuse portion d’adjectifs.

2. Produire plus vite permet aussi de produire des erreurs plus vite

L’IA peut inventer une API, utiliser une proposition non standardisée, mélanger les comportements de plusieurs runtimes ou ajouter une dépendance disproportionnée. Elle peut générer du code plausible, c’est-à-dire exactement le type de code qui passe une revue distraite.

Le risque n’est pas seulement l’erreur visible. C’est la dette logicielle produite à haute vitesse : duplications, abstractions prématurées, tests décoratifs, dépendances inutiles et gestion incomplète des erreurs. L’automatisation a toujours été excellente pour accélérer. Elle demande seulement de préciser dans quelle direction.

3. Les tests et les outils d’analyse deviennent encore plus importants

Le code généré doit passer par les mêmes contrôles que le code humain : formatage, analyse statique, typage, tests unitaires, tests d’intégration, vérification de sécurité et revue du changement.

Une réponse d’IA n’est pas une preuve. Un test qui échoue est une information. Un test qui passe est une information meilleure, mais limitée aux cas couverts. Et cent pour cent de couverture ne prouve pas que les assertions racontent quelque chose d’intéressant. On peut parfaitement tester avec une précision industrielle que la mauvaise fonction produit le mauvais résultat attendu.

4. Comprendre le langage reste nécessaire pour relire

L’IA réduit l’effort de rappel, pas le besoin de jugement. Pour vérifier une fonction asynchrone, il faut comprendre les promesses, les erreurs et la concurrence. Pour repérer une copie superficielle, il faut comprendre les références. Pour évaluer un champ privé, il faut comprendre les classes. Pour juger une API récente, il faut connaître le niveau de compatibilité attendu.

Autrement dit, l’IA peut tenir le clavier. Elle ne signe pas l’incident de production.

Une méthode raisonnable pour travailler avec l’IA sur du JavaScript moderne

Un processus utile tient en six étapes.

  1. Définir la cible. Préciser le navigateur minimal, la version du runtime, le format de modules et l’usage éventuel de TypeScript.
  2. Donner le contexte utile. Fournir les interfaces, conventions, exemples d’entrée et de sortie, ainsi que les contraintes métier.
  3. Limiter le changement. Demander un diff minimal et interdire les nouvelles dépendances sauf justification.
  4. Exiger des tests significatifs. Couvrir les cas normaux, les limites, les erreurs et les comportements asynchrones.
  5. Exécuter les contrôles. Lancer le typage, le linter, les tests et la construction réelle du projet.
  6. Relire le raisonnement. Vérifier l’API, la compatibilité, les erreurs, la sécurité, les performances et la maintenabilité.

Un prompt technique devient plus fiable lorsqu’il fixe des contraintes observables :

Implémente cette fonction en JavaScript ESM pour Node.js 24, sans nouvelle dépendance. Conserve l’API publique, gère l’annulation et les erreurs HTTP, ajoute des tests pour les réponses 200, 404, 500 et le dépassement de délai. N’utilise que des fonctionnalités disponibles dans cette cible et présente le changement sous forme de diff minimal.

Ce prompt ne garantit pas un bon résultat. Il réduit simplement l’espace disponible pour l’improvisation. C’est déjà beaucoup, y compris avec certains collègues humains.

Ce qu’un développeur revenu de 2015 doit apprendre en priorité

Il n’est pas nécessaire d’étudier chaque édition comme une collection de timbres. L’ordre suivant couvre l’essentiel du JavaScript professionnel moderne.

Niveau 1 — indispensable

  • const, let et portée de bloc ;
  • fonctions fléchées et comportement de this ;
  • chaînes de gabarit ;
  • déstructuration, paramètres par défaut, rest et spread ;
  • modules import/export ;
  • promesses, async/await et gestion des erreurs ;
  • méthodes de tableaux : map, filter, reduce, find, some, every ;
  • Map, Set et for...of ;
  • chaînage optionnel ?. et coalescence ??.

Niveau 2 — très utile dans les projets actuels

  • imports dynamiques et découpage du code ;
  • Promise.all(), allSettled(), any() et race() ;
  • champs de classes, champs privés et méthodes statiques ;
  • méthodes de copie des tableaux ;
  • Object.entries(), Object.fromEntries() et Object.groupBy() ;
  • itérateurs et itérateurs asynchrones ;
  • bases de TypeScript et de l’analyse statique ;
  • compréhension de la cible d’exécution et de la compatibilité.

Niveau 3 — à apprendre lorsqu’un besoin apparaît

  • générateurs avancés ;
  • Proxy, Reflect et métaprogrammation ;
  • WeakMap, WeakSet, WeakRef et FinalizationRegistry ;
  • tableaux typés, mémoire partagée et Atomics ;
  • fonctionnalités fines des expressions régulières ;
  • méthodes ES2025 et ES2026 encore inégalement déployées dans les environnements pris en charge.

Moderniser un ancien projet sans déclencher un programme spatial

La modernisation peut suivre une progression contrôlée.

  1. Établir le comportement existant. Ajouter ou stabiliser les tests avant de modifier la syntaxe.
  2. Définir les environnements cibles. Navigateurs, versions de Node.js, appareils embarqués ou autres runtimes déterminent les choix possibles.
  3. Automatiser le style. Un formateur et un linter réduisent les débats et détectent des constructions risquées.
  4. Remplacer var avec attention. La portée et le hissage — le traitement des déclarations avant l’exécution — peuvent modifier le comportement.
  5. Passer aux modules par frontières claires. Éviter la conversion simultanée de tout le dépôt si les dépendances sont emmêlées.
  6. Moderniser l’asynchronisme avec des tests. Une conversion mécanique de rappels vers async/await peut changer l’ordre et la gestion des erreurs.
  7. Réduire les dépendances devenues inutiles. Vérifier d’abord la compatibilité et l’impact sur le bundle.
  8. Introduire TypeScript progressivement si le projet en bénéficie. Commencer par les interfaces publiques et les zones à risque.
  9. Utiliser l’IA sur des changements bornés. Une fonction, un module ou une migration vérifiable vaut mieux qu’un ordre héroïque comme « modernise toute l’application ».
  10. Mesurer avant d’optimiser. Temps de démarrage, taille livrée, mémoire et latence doivent être observés, pas devinés avec intensité.

Alors, qu’est-ce qui a vraiment changé ?

JavaScript a moins changé par accumulation de gadgets que par construction d’un vocabulaire cohérent.

  • ES2015 a donné une architecture moderne au langage : portée de bloc, modules, classes, promesses, itérables et syntaxe expressive.
  • ES2017 a rendu l’asynchronisme nettement plus lisible avec async/await.
  • ES2020 a supprimé plusieurs irritants quotidiens avec ?., ??, BigInt et de meilleurs outils pour les promesses et les modules.
  • ES2022 a donné aux classes de vrais champs privés et permis await au niveau supérieur des modules.
  • ES2023 à ES2026 ont enrichi les transformations immuables, le regroupement, les ensembles, les itérateurs, les données binaires, JSON et les calculs précis.
  • L’écosystème a déplacé une partie de la complexité vers les types, les outils, les runtimes et les chaînes de construction.
  • L’IA a diminué le coût du premier jet, mais augmenté l’importance du contexte, des tests, de la revue et de la responsabilité technique.

Le développeur de 2026 n’a donc pas besoin de réciter ECMA-262. Il doit savoir reconnaître les bons outils, comprendre leurs limites, vérifier leur compatibilité et relire du code produit plus vite qu’avant. La compétence rare n’est plus seulement d’écrire une boucle. C’est de savoir si cette boucle devait exister, si elle traite les bons cas et si la machine qui l’a proposée n’a pas discrètement importé un paquet abandonné pour additionner deux nombres.

Sources et pistes d’approfondissement