ES6 : écrire du JavaScript qui nous ressemble enfin
Pendant des années, coder en JavaScript ressemblait parfois à du bricolage organisé. On faisait avec les moyens du bord : des variables qui changent de valeur sans prévenir, des fonctions copieuses qu’on recopiait un peu partout, des concaténations de chaînes à rallonge… Bref, un langage souple, mais vite confus.
Depuis 2015, avec l’arrivée d’ES6 (aussi appelé ECMAScript 2015), JavaScript a gagné une nouvelle grammaire. Plus claire, plus stable, mieux adaptée aux vrais projets. De nouveaux outils — let
, const
, fonctions fléchées, déstructuration, import/export
— nous permettent d’écrire un code plus lisible, plus fiable, et surtout plus facile à maintenir. Non pas pour faire « moderne », mais pour faire mieux.
Dans cet article, nous allons voir pourquoi cette syntaxe moderne a réellement changé la donne. Pas en listant toutes les nouveautés, mais en comparant des cas concrets : l’ancienne manière de faire, et ce que propose désormais ES6 pour transformer les gestes simples du quotidien. À chaque fois, une comparaison : l’avant, l’après, et ce que cela change dans la tête de celui qui code.
Pour ceux qui souhaitent le détail des nouveautés
L’objectif de cet article n’est pas de passer en revue toutes les nouveautés d’ES6, ni d’en faire une fiche technique. D’autres l’ont déjà très bien fait — et nous vous conseillons vivement d’y jeter un œil si ce n’est pas encore le cas. Pour comprendre la syntaxe, les règles, les cas limites, les meilleurs points de départ restent :
- MDN Web Docs – pour des explications claires et à jour, avec des exemples solides ;
- Human Who Codes – le site de Nicholas C. Zakas, une des voix les plus cohérentes autour de JavaScript ;
- JavaScript Tutorial (javascript.info) – accessible, structuré, avec une progression bien pensée.
Dans cet article, nous allons simplement nous poser la question : que changent ces outils quand on écrit du code au quotidien ? Et en quoi la syntaxe ES6 nous aide à mieux faire ce qu’on faisait déjà, sans changer notre manière de penser… mais en l’éclaircissant.
Avant ES6, on définissait une fonction comme on empilait une boîte dans une étagère déjà pleine : il fallait créer la structure complète, même pour une action triviale.
// Avant ES6
var toUpperCase = function(name) {
return name.toUpperCase();
};
Ça marche, bien sûr. Mais pour une opération aussi simple, la forme prend plus de place que le fond. On répète inutilement function
, on ouvre des accolades, on écrit return
, alors que l’intention est limpide. Le sens se noie dans la syntaxe. Avec ES6, on peut écrire la même chose autrement — sans bruit inutile :
// Avec ES6
const toUpperCase = name => name.toUpperCase();
La fonction fléchée n’est pas là pour faire “tendance” : elle permet d’exprimer une idée simple sans se cacher derrière des formules.
- La déclaration est directe :
name
est un paramètre, la flèche indique ce qu’on en fait. - Pas de
return
ni d’accolades : le résultat est retourné automatiquement. - Et surtout, on voit immédiatement que cette fonction est pure, sans effet de bord.
Ce que ça change dans le quotidien ? On n’hésite plus à écrire des petites fonctions intermédiaires. On lit ce que fait la fonction sans dérouler trois lignes. Et dans des callbacks ou des .map()
, .filter()
, .find()
, le regard ne bute plus sur la syntaxe. C’est toujours JavaScript, mais avec un style plus franc. Moins de formes, plus de fond.
Dire ce qu’on veut récupérer, pas ce qu’on va chercher
Quand on manipule un objet ou un tableau, il est courant de vouloir extraire quelques données pour les utiliser à part. Avant ES6, on procédait par assignations successives, souvent verbeuses, avec des chaînes de points qui finissent par gêner la lecture.
// Avant ES6
const user = { name: "Alice", age: 32 };
const name = user.name;
const age = user.age;
Ça fonctionne, mais ça ne dit pas grand-chose sur l’intention. On lit chaque ligne comme une opération mécanique, alors qu’on pourrait simplement dire je veux récupérer name et age depuis user, sans s’embarrasser.
// Avec ES6
const user = { name: "Alice", age: 32 };
const { name, age } = user;
La syntaxe est plus dense, mais elle est surtout plus explicite. On indique directement ce qu’on veut extraire, et cela se voit d’un coup d’œil. On ne suit plus une structure, on lit une intention.
Ce confort est encore plus net quand on ne garde qu’une partie des données, ou qu’on renomme une clé pour l’utiliser autrement. Le code s’épure, les traitements se lisent comme des déclarations, pas comme des détours.
// Objet source
const user = {
name: "Alice",
age: 32,
email: "alice@example.com"
};
// Déstructuration avec renommage
const { name: userName } = user;
// La variable userName contient maintenant la valeur de user.name
console.log(userName); // "Alice"
// On peut ainsi utiliser userName dans un autre contexte,
// sans collision avec une autre variable "name"
const display = `Bienvenue, ${userName}!`;
console.log(display); // "Bienvenue, Alice!"
Ce type de renommage est très utile quand on veut clarifier le rôle d’une donnée extraite, ou quand on travaille avec plusieurs objets ayant des clés similaires. Cela évite les conflits de noms tout en restant concis et lisible.
Manipuler sans dupliquer : faire circuler les valeurs
Avant ES6, pour copier un tableau ou un objet, on passait souvent par des méthodes spécifiques, ou pire, par des boucles manuelles. Modifier une structure sans altérer l’original demandait de l’anticipation, parfois au prix de la clarté ou de la performance.
// Avant ES6
const original = ["a", "b", "c"];
const copy = original.slice(); // on crée une copie du tableau
copy.push("d"); // on modifie la copie
console.log("original :", original); // ["a", "b", "c"]
console.log("copy :", copy); // ["a", "b", "c", "d"]
Cela confirme que original
est resté intact, mais l’opération demande de connaître slice()
et ce qu’il fait. Avec le spread operator, la même intention devient plus lisible :
// Avec ES6
const original = ["a", "b", "c"];
const copy = [...original, "d"]; // on crée une copie et on ajoute un élément
console.log("original :", original); // ["a", "b", "c"]
console.log("copy :", copy); // ["a", "b", "c", "d"]
Le geste devient clair et immédiat. On duplique le tableau d’un côté, on y ajoute une valeur de l’autre. On voit la construction, on visualise le résultat, on évite les effets de bord.
L’opérateur ...
permet aussi de fusionner deux objets sans casser l’un ou l’autre, ce qui facilite les ajustements locaux.
const base = { role: "user", active: true };
const custom = { ...base, role: "admin" };
console.log("base :", base); // { role: "user", active: true }
console.log("custom :", custom); // { role: "admin", active: true }
L’objet custom
reprend tout ce que contient base
, mais redéfinit la propriété role
au passage. C’est une manière simple d’adapter une configuration sans réécrire ce qu’on garde.
Inversement, l’opérateur peut servir à regrouper ce qu’on ne veut pas nommer en détail. On peut par exemple extraire les premiers éléments d’un tableau, et rassembler tout ce qui reste dans une seule variable.
const [first, ...others] = ["a", "b", "c", "d"];
console.log("first :", first); // "a"
console.log("others :", others); // ["b", "c", "d"]
Ce genre d’écriture permet de traiter un tableau en deux temps : d’un côté ce qu’on capte immédiatement, de l’autre ce qu’on laisse de côté pour plus tard.
Pas besoin de .slice()
, ni de jongler avec des index.
Et si on veut récupérer plusieurs valeurs précises au début, la logique reste la même :
const [first, second, ...others] = ["a", "b", "c", "d"];
console.log("first :", first); // "a"
console.log("second :", second); // "b"
console.log("others :", others); // ["c", "d"]
On prend ce dont on a besoin, on range le reste. C’est une écriture qui allège les découpages, rend les intentions plus visibles, et surtout évite d’écrire trois lignes quand une seule suffit.
Nommer clairement l’intention : let ou const, mais plus var
Avant ES6, on déclarait toutes les variables avec var
, parfois sans trop y réfléchir. Une variable de boucle, une config temporaire, une valeur stable… tout passait par le même mot-clé. Et le problème, c’est que var
ne disait rien sur ce qu’on voulait vraiment.
var total = 0;
var items = ["a", "b", "c"];
Le mot est neutre, et la portée est floue. On peut réassigner, redéclarer, accéder à la variable avant même qu’elle soit définie. Et dans des fonctions ou blocs imbriqués, cela rend le comportement difficile à anticiper.
var total = 0;
if (true) {
var total = 42;
console.log("dans le bloc :", total); // 42
}
console.log("hors du bloc :", total); // 42
La variable déclarée dans le bloc écrase celle de l’extérieur, sans alerte, sans isolation. Avec let
ou const
, le comportement est plus clair : le bloc crée sa propre portée.
let total = 0;
if (true) {
let total = 42;
console.log("dans le bloc :", total); // 42
}
console.log("hors du bloc :", total); // 0
Les deux variables sont bien distinctes, et cela permet d’écrire un code plus prévisible. Ce n’est pas qu’une subtilité technique, c’est un filet de sécurité dans les traitements quotidiens.
Avec ES6, on peut aussi clarifier ce qu’on attend d’une variable dès sa déclaration. Si elle est amenée à changer, on utilise let
. Si elle ne doit pas être remplacée, on choisit const
. C’est aussi simple — et aussi utile — que ça.
let compteur = 0; // évolutif, volontairement réaffecté
const seuil = 10; // stable, ne changera pas de référence
compteur++; // OK
seuil = 20; // Erreur : Assignment to constant variable
seuil++; // Erreur aussi : tentative de réaffectation
Ce n’est pas juste une sécurité. C’est une manière de rendre l’intention lisible. On sait d’un coup d’œil si une donnée va évoluer ou non, sans attendre de tomber sur une ligne de réaffectation dix fichiers plus loin.
Et avec un tableau, c’est encore un peu plus subtil. Déclarer un tableau en const
ne le rend pas immuable, mais empêche simplement qu’on le remplace par un autre.
const notes = [10, 12, 14];
notes.push(16); // OK : on modifie le contenu
notes[1] = 13; // OK : on écrase une valeur
notes = [15, 17]; // Erreur : on tente de réassigner la variable
On peut donc modifier les éléments d’un tableau ou d’un objet déclaré avec const
, tant qu’on ne touche pas à sa référence. Cette nuance permet de garder une certaine souplesse dans les traitements, sans perdre le contrôle sur ce qu’on a déclaré.
Assembler son code sans tout charger en vrac
Avant ES6, partager du code entre fichiers JavaScript demandait un peu d’astuce. On utilisait script
dans le HTML, on empilait les fichiers dans le bon ordre, et on croisait les doigts pour que tout soit chargé quand il le fallait.
<!-- Avant ES6 : inclusion à la main -->
<script src="utils.js"></script>
<script src="main.js"></script>
Dans les fichiers eux-mêmes, il n’y avait pas de mot-clé clair pour dire ce qu’on voulait exposer ou importer. On ajoutait des fonctions ou des objets à la main dans l’espace global, sans protection :
// utils.js
function formatDate(date) {
return date.toLocaleDateString();
}
// main.js
console.log(formatDate(new Date())); // fonctionne si utils.js a été chargé avant
Le problème, c’est qu’il n’y a aucun lien explicite entre les fichiers. On dépend de l’ordre, et tout vit dans le même espace global, ce qui rend les conflits ou les oublis difficiles à éviter. Avec ES6, le système de modules permet de déclarer clairement ce qu’un fichier fournit, et ce qu’un autre consomme.
// utils.js
export function formatDate(date) {
return date.toLocaleDateString();
}
// main.js
import { formatDate } from "./utils.js";
console.log(formatDate(new Date())); // "21/06/2025"
Cette fois, le lien est explicite et vérifiable. Si formatDate
n’est pas exportée, l’import échoue. On ne travaille plus à découvert dans un espace global, mais dans des modules isolés, clairs, et auto-documentés. On ne charge plus tout en vrac, on choisit ce qu’on importe, et on sait d’où ça vient. On peut aussi exporter plusieurs éléments d’un même fichier, et n’en importer que certains :
// utils.js
export const appName = "My App";
export function formatDate(date) { /* ... */ }
// main.js
import { formatDate } from "./utils.js";
console.log(formatDate(new Date())); // OK
console.log(appName); // Erreur : non importée ici
Pour éviter cette erreur, il suffisait d’importer appName
comme les autres :
// main.js
import { formatDate, appName } from "./utils.js";
console.log(formatDate(new Date()));
console.log(appName); // "My App"
Et si l’on souhaite tout regrouper dans un même espace de noms, on peut importer l’ensemble sous forme d’objet :
// main.js
import * as utils from "./utils.js";
console.log(utils.formatDate(new Date()));
console.log(utils.appName);
C’est utile pour garder le code lisible, surtout lorsqu’on utilise plusieurs fonctions d’un même module. On sait d’un coup d’œil que tout vient du même endroit, sans collision possible.
Fonctions fléchées : éviter les pièges de this
Les fonctions fléchées ne font pas que raccourcir l’écriture. Elles changent aussi le comportement de this
: elles ne le redéfinissent pas. Et dans un projet, ça évite bien des malentendus. Prenons un objet très simple, avec une méthode censée accéder à sa propre donnée interne après un petit délai :
// Avec une fonction classique
const journal = {
titre: "Mon journal",
afficher() {
setTimeout(function () {
console.log(this.titre); // undefined
}, 500);
}
};
journal.afficher();
Le this
à l’intérieur de la fonction classique ne pointe pas vers journal
. Il pointe vers window
(ou undefined
en mode strict). Du coup, this.titre
est introuvable. Avec une fonction fléchée, le comportement devient prévisible :
// Avec une fonction fléchée
const journal = {
titre: "Mon journal",
afficher() {
setTimeout(() => {
console.log(this.titre); // "Mon journal"
}, 500);
}
};
journal.afficher();
Cette fois, this
garde la valeur du contexte extérieur, ici l’objet journal
. On évite les effets de bord, les bind(this)
, les variables d’appoint comme const self = this
. La fonction fait ce qu’on attend, sans détour.
Des chaînes qui se lisent d’un seul trait
Avant ES6, écrire une phrase dynamique demandait de jouer avec les guillemets, les +
, et les conversions implicites. C’était faisable, mais pas toujours agréable à relire.
// Avant ES6
const user = "Lucie";
const age = 28;
console.log("L’utilisateur " + user + " a " + age + " ans.");
// "L’utilisateur Lucie a 28 ans."
Chaque morceau est séparé, fragmenté, concaténé. Ce n’est pas dramatique… mais c’est moins fluide, surtout quand les expressions deviennent plus longues ou conditionnelles. Depuis ES6, on peut écrire en continu, comme si on parlait. Il suffit d’utiliser les backticks (`
) et l’interpolation ${}
:
// Avec ES6
const user = "Lucie";
const age = 28;
console.log(`L’utilisateur ${user} a ${age} ans.`);
// "L’utilisateur Lucie a 28 ans."
La différence est subtile, mais décisive. Le texte se lit d’un seul bloc. Il n’y a plus de guillemets qui s’ouvrent et se ferment, plus de +
entre les mots, plus d’hésitation sur les espaces ou les types. Et on peut y glisser des expressions plus complexes sans s’emmêler :
const total = 14;
const seuil = 10;
console.log(`Total : ${total} (${total > seuil ? "dépassement" : "ok"})`);
// "Total : 14 (dépassement)"
Le message devient à la fois plus clair et plus flexible. Ce n’est pas juste une syntaxe plus moderne : c’est une syntaxe qui aide à penser droit.
Bonus : voir tout de suite ce qu’on cherche
Certains outils d’ES6 ne sont pas révolutionnaires, mais ils font gagner en clarté. Prenons un cas tout simple : vérifier qu’une valeur n’est pas dans un tableau.
// Avant ES6
const noms = ["Alice", "Lucie", "Marwan"];
if (noms.indexOf("Jean") === -1) {
console.log("Jean est absent.");
}
Ce code fonctionne, mais il faut réfléchir à l’intention. On cherche si la valeur n’existe pas… en comparant sa position à -1. Depuis ES6, includes()
permet d’écrire ce qu’on pense :
const noms = ["Alice", "Lucie", "Marwan"];
if (!noms.includes("Jean")) {
console.log("Jean est absent.");
}
Même résultat, mais plus lisible. On lit « si noms n’inclut pas Jean », sans avoir à se rappeler ce que signifie -1
. C’est un détail, bien sûr. Mais ce sont souvent ces détails qui rendent un code agréable à lire, même quand il n’a rien de compliqué.
Un atelier qui ne déborde plus
Passer à ES6, ce n’est pas apprendre un nouveau métier. C’est juste ranger son établi. On n’empile plus les outils au hasard : chaque fonction a sa place, chaque variable dit ce qu’elle est censée faire. On ouvre un tiroir, on y trouve ce qu’on attend, et on sait qu’on ne risque pas de tout renverser en manipulant un objet.
Let et const nous protègent sans alourdir. Les fonctions fléchées nous évitent des contorsions. Les chaînes interpolées, les objets clonés, les imports ciblés : tout ça ne sert qu’à une chose… Mieux lire ce qu’on écrit. Et au final, c’est ça qui fait la différence entre du code qu’on redoute de rouvrir, et du code qu’on comprend d’un regard.
Écrire mieux, comprendre plus vite
Adopter la syntaxe ES6, ce n’est pas une question de mode. Ce n’est pas pour « faire moderne », ni pour coller à une tendance. C’est pour faire mieux : en lisant plus vite, en réutilisant plus proprement, en écrivant avec moins d’hésitation.
On n’a pas besoin de tout connaître pour s’y mettre. Même quelques ajustements dans un script existant suffisent à changer l’ambiance : moins de pièges, moins d’ambiguïtés, un code plus lisible, plus serein, pour soi comme pour les autres.