À propos de ce cours
Cette leçon explique comment extraire des valeurs d'un objet ou d'un tableau avec la déstructuration : renommage, valeurs par défaut, objets imbriqués, reste des propriétés et paramètres de fonction. Vous apprendrez à copier, fusionner et compléter des tableaux et des objets avec l'opérateur spread, et à regrouper des valeurs avec l'opérateur rest, toujours placé en dernier. La différence entre copie superficielle et copie profonde (structuredClone) est illustrée par un schéma. L'immutabilité est appliquée aux mises à jour de listes, comme dans Angular avec les signals et les formulaires. Des schémas et cinq activités pratiques accompagnent chaque notion.
-
Objectifs
- Extraire des valeurs d'un objet ou d'un tableau avec la déstructuration
- Renommer, donner une valeur par défaut, déstructurer un objet imbriqué
- Déstructurer les paramètres d'une fonction
- Copier, fusionner et compléter des tableaux et des objets avec l'opérateur spread
... - Regrouper des valeurs avec l'opérateur rest
... - Distinguer copie superficielle et copie profonde
- Mettre à jour des données sans les modifier (immutabilité), comme dans Angular
-
Présentation
- Ces trois notions sont apparues avec ES6 (ES2015) pour les tableaux et avec ES2018 pour les objets. Elles permettent de manipuler des données en écrivant beaucoup moins de code :
- la déstructuration extrait des valeurs d'un objet ou d'un tableau et les range dans des variables, en une seule instruction ;
- l'opérateur spread (« étaler ») déplie un tableau ou un objet en éléments séparés ;
- l'opérateur rest (« reste ») fait l'inverse : il regroupe plusieurs valeurs dans un tableau ou un objet.
- Spread et rest s'écrivent de la même façon, avec trois points
.... C'est l'endroit où ils sont écrits qui indique leur rôle. - Dans Angular, ces notions sont omniprésentes : lecture des données d'un formulaire, réponses d'une API, mise à jour d'une liste ou d'un signal sans modifier l'original.
- Ces trois notions sont apparues avec ES6 (ES2015) pour les tableaux et avec ES2018 pour les objets. Elles permettent de manipuler des données en écrivant beaucoup moins de code :
-
La déstructuration d'un objet
- On écrit entre accolades, à gauche du
=, les noms des propriétés à extraire. Chaque propriété citée devient une variable du même nom.
- On écrit entre accolades, à gauche du
const stagiaire = {
nom: 'Ben Ali',
prenom: 'Amira',
groupe: 'DEV1',
moyenne: 15.5,
adresse: { ville: 'Tunis', codePostal: '1002' },
modules: ['HTML', 'CSS', 'Angular'],
};
// Avant ES6
var nom1 = stagiaire.nom;
var prenom1 = stagiaire.prenom;
// Avec la déstructuration
const { nom, prenom } = stagiaire;
console.log(nom, prenom); // Ben Ali Amira

- Renommer:
propriété: nouveauNom. - Valeur par défaut:
propriété = valeur, utilisée seulement si la propriété est absente (undefined). - Les deux se combinent :
propriété: nouveauNom = valeur.
const { groupe: classe } = stagiaire; // renommer
const { telephone = 'non renseigné', moyenne = 0 } = stagiaire; // valeurs par défaut
const { bourse: montantBourse = 0 } = stagiaire; // renommer + défaut
console.log(classe, telephone, moyenne, montantBourse);
// DEV1 non renseigné 15.5 0
- Objet imbriqué: on reprend la même forme que l'objet. Attention : seule la variable finale est créée.
const { adresse: { ville } } = stagiaire;
console.log(ville); // Tunis
// console.log(adresse); // ReferenceError: adresse is not defined
const { modules: [premierModule] } = stagiaire; // tableau dans un objet
console.log(premierModule); // HTML
- Le reste des propriétés (rest, ES2018) :
...resteregroupe toutes les propriétés non citées dans un nouvel objet. C'est une façon simple de retirer des propriétés.
const { adresse, modules, ...identite } = stagiaire;
console.log(identite);
// { nom: 'Ben Ali', prenom: 'Amira', groupe: 'DEV1', moyenne: 15.5 }
- Nom de propriété calculé: quand le nom est dans une variable, on l'écrit entre crochets.
const cle = 'groupe';
const { [cle]: valeur } = stagiaire;
console.log(valeur); // DEV1
- Remarque : pour déstructurer dans des variables déjà déclarées, il faut entourer l'instruction de parenthèses, sinon les accolades sont lues comme un bloc :
({ nom: a } = stagiaire);.
La déstructuration d'un tableau
- On écrit entre crochets les variables à remplir. Ici, c'est la position qui compte, pas le nom.

const podium = ['Karim', 'Amira', 'Yasmine', 'Sami', 'Ali'];
const [premier, deuxieme] = podium; // les deux premiers
console.log(premier, deuxieme); // Karim Amira
const [, , troisieme] = podium; // une virgule seule saute un élément
console.log(troisieme); // Yasmine
const [major, ...autres] = podium; // rest : le reste dans un tableau
console.log(major, autres); // Karim [ 'Amira', 'Yasmine', 'Sami', 'Ali' ]
const [x = 'aucun', y = 'aucun', z = 'aucun'] = ['A']; // valeurs par défaut
console.log(x, y, z); // A aucun aucun
let g1 = 'DEV1', g2 = 'DEV2';
[g1, g2] = [g2, g1]; // échanger deux variables
console.log(g1, g2); // DEV2 DEV1
- La déstructuration de tableau fonctionne avec tout ce qui se parcourt (itérable) : chaînes,
Set,Map, résultats desplit()…
const [heures, minutes] = '09:30'.split(':').map(Number);
console.log(heures, minutes); // 9 30
const [p, q, ...r] = 'Angular';
console.log(p, q, r); // A n [ 'g', 'u', 'l', 'a', 'r' ]
const [[a1], [, b2]] = [[1, 2], [3, 4]]; // tableaux imbriqués
console.log(a1, b2); // 1 4
-
Déstructurer les paramètres d'une fonction
- C'est l'usage le plus courant : la fonction reçoit un objet et en extrait directement ce dont elle a besoin. L'appel devient lisible et l'ordre des propriétés n'a plus d'importance.
const presenter = ({ prenom, nom, groupe = 'non affecté' }) => `${prenom} ${nom} (${groupe})`;
console.log(presenter(stagiaire)); // Amira Ben Ali (DEV1)
console.log(presenter({ prenom: 'Sami', nom: 'Trabelsi' })); // Sami Trabelsi (non affecté)
presenter();
// TypeError: Cannot destructure property 'prenom' of 'undefined' as it is undefined.
- Pour qu'une fonction puisse être appelée sans argument, on donne aussi une valeur par défaut à l'objet entier :
= {}. C'est le modèle classique des fonctions à options:
const creerBouton = ({ texte = 'Valider', couleur = 'primary', taille = 'md' } = {}) =>
`<button class="btn btn-${couleur} btn-${taille}">${texte}</button>`;
console.log(creerBouton());
// <button class="btn btn-primary btn-md">Valider</button>
console.log(creerBouton({ couleur: 'danger', texte: 'Supprimer' }));
// <button class="btn btn-danger btn-md">Supprimer</button>
- Une fonction peut aussi renvoyer plusieurs valeurs dans un objet, que l'appelant déstructure :
const statistiques = (notes) => ({
min: Math.min(...notes),
max: Math.max(...notes),
moyenne: notes.reduce((s, n) => s + n, 0) / notes.length,
});
const { min, max, moyenne: moy } = statistiques([12, 8, 17, 15]);
console.log(min, max, moy); // 8 17 13
- Enfin, on déstructure souvent dans les boucles
for…of:
for (const { nom, moyenne } of [{ nom: 'Amira', moyenne: 15.5 }, { nom: 'Sami', moyenne: 9.75 }]) {
console.log(`${nom} : ${moyenne}`);
}
for (const [module, coefficient] of Object.entries({ Angular: 3, Flutter: 2 })) {
console.log(module, coefficient);
}
-
L'opérateur spread sur les tableaux
- Placé devant un tableau,
...le déplie: ses éléments sont insérés un par un à cet endroit.
- Placé devant un tableau,

const dev1 = ['Amira', 'Yasmine'];
const dev2 = ['Sami', 'Karim'];
// Copier : un NOUVEAU tableau
const copie = [...dev1];
copie.push('Ali');
console.log(dev1, copie, copie === dev1);
// [ 'Amira', 'Yasmine' ] [ 'Amira', 'Yasmine', 'Ali' ] false
// Fusionner et insérer
const tous = [...dev1, ...dev2]; // [ 'Amira', 'Yasmine', 'Sami', 'Karim' ]
const avecNouveaux = ['Nour', ...dev1, 'Ali']; // [ 'Nour', 'Amira', 'Yasmine', 'Ali' ]
// Passer les éléments comme arguments séparés
const notes = [12, 8, 17, 15];
console.log(Math.max(...notes), Math.min(...notes)); // 17 8
// Transformer un itérable en tableau
console.log([...new Set(['JS', 'TS', 'JS', 'HTML'])]); // [ 'JS', 'TS', 'HTML' ] : doublons supprimés
console.log([...'DEV1']); // [ 'D', 'E', 'V', '1' ]
-
L'opérateur spread sur les objets
- Placé dans un objet littéral
{ },...objetrecopie toutes ses propriétés. Si une propriété apparaît plusieurs fois, la dernière l'emporte: l'ordre d'écriture compte.
- Placé dans un objet littéral
const base = { nom: 'Sami', groupe: 'DEV1', actif: true };
const copieObj = { ...base };
console.log(copieObj === base); // false : nouvel objet
const misAJour = { ...base, groupe: 'DEV2' }; // groupe remplacé : DEV2
const ecrase = { groupe: 'DEV2', ...base }; // base écrit en dernier : DEV1
console.log(misAJour.groupe, ecrase.groupe); // DEV2 DEV1
const avecEmail = { ...base, email: 'sami@formaplus.tn' }; // ajouter une propriété
const { actif, ...sansActif } = base; // retirer une propriété (rest)
console.log(sansActif); // { nom: 'Sami', groupe: 'DEV1' }
// Fusionner des réglages par défaut avec les choix de l'utilisateur
const defauts = { theme: 'clair', langue: 'fr', notifications: true };
const choixUtilisateur = { theme: 'sombre' };
console.log({ ...defauts, ...choixUtilisateur });
// { theme: 'sombre', langue: 'fr', notifications: true }
-
Copie superficielle et copie profonde
- Spread fait une copie superficielle: seul le premier niveau est copié. Les objets et tableaux imbriqués restent partagés entre l'original et la copie.

const formation = { titre: 'Web', formateur: { nom: 'Ali' }, modules: ['HTML'] };
const copie = { ...formation };
copie.titre = 'Réseaux'; // premier niveau : l'original ne change pas
copie.formateur.nom = 'Mouna'; // objet imbriqué partagé : l'original change !
copie.modules.push('CSS'); // tableau imbriqué partagé : l'original change !
console.log(formation);
// { titre: 'Web', formateur: { nom: 'Mouna' }, modules: [ 'HTML', 'CSS' ] }
- Deux solutions :
structuredClone()fait une copie profonde: tous les niveaux sont copiés ;- pour modifier un seul niveau imbriqué, on étale aussi ce niveau.
const formation2 = { titre: 'Web', formateur: { nom: 'Ali' }, modules: ['HTML'] };
const profonde = structuredClone(formation2);
profonde.formateur.nom = 'Karim';
console.log(formation2.formateur.nom, profonde.formateur.nom); // Ali Karim
const modifiee = { ...formation2, formateur: { ...formation2.formateur, nom: 'Hédi' } };
console.log(formation2.formateur.nom, modifiee.formateur.nom); // Ali Hédi
-
L'opérateur rest dans les paramètres
- Dans la liste des paramètres d'une fonction,
...nomregroupe tous les arguments restants dans un vrai tableau. Il remplace l'ancien objetarguments, qui n'existe pas dans les fonctions fléchées. - Le paramètre rest est toujours le dernier.
- Dans la liste des paramètres d'une fonction,
const moyenneDe = (...notes) => notes.reduce((s, n) => s + n, 0) / notes.length;
console.log(moyenneDe(12, 14), moyenneDe(10, 15, 20)); // 13 15
const inscrire = (formation, ...stagiaires) =>
`${formation} : ${stagiaires.length} inscrit(s) – ${stagiaires.join(', ')}`;
console.log(inscrire('Angular', 'Amira', 'Sami', 'Karim'));
// Angular : 3 inscrit(s) – Amira, Sami, Karim
// Spread et rest ensemble : passer un tableau à une fonction rest
const notesDuGroupe = [10, 15, 20];
console.log(moyenneDe(...notesDuGroupe)); // 15
// function inscrire(...stagiaires, formation) {}
// SyntaxError: Rest parameter must be last formal parameter
-
Mettre à jour sans modifier : l'immutabilité
- Être immutable, c'est ne jamais modifier une donnée existante, mais créer une nouvelle version. Spread,
mapetfilterle permettent pour les trois opérations courantes sur une liste :
- Être immutable, c'est ne jamais modifier une donnée existante, mais créer une nouvelle version. Spread,
const panier = [
{ id: 1, nom: 'Clavier', quantite: 1 },
{ id: 2, nom: 'Souris', quantite: 2 },
];
// Ajouter : au lieu de push()
const ajoute = [...panier, { id: 3, nom: 'Écran', quantite: 1 }];
// Modifier un élément : map + spread de l'élément concerné
const modifie = panier.map((a) => (a.id === 2 ? { ...a, quantite: a.quantite + 1 } : a));
// Supprimer : au lieu de splice()
const supprime = panier.filter((a) => a.id !== 1);
console.log(panier.length, ajoute.length); // 2 3 : l'original n'a pas changé
console.log(modifie[1].quantite, panier[1].quantite); // 3 2
console.log(modifie[0] === panier[0]); // true : l'élément non modifié est réutilisé
- Pourquoi c'est important dans Angular : Angular détecte un changement en comparant les références. Un nouveau tableau ou un nouvel objet est un signal clair : « la donnée a changé, il faut mettre la vue à jour ». C'est indispensable avec les signals, avec les composants optimisés (
OnPush) et avec les pipes.
Destructuration, spread et rest dans Angular
- Quelques situations que vous rencontrerez dans les chapitres suivants :
// Mettre à jour un signal contenant une liste, sans la modifier
this.panier.update((articles) => [...articles, nouvelArticle]);
// Modifier un élément d'une liste stockée dans le composant
this.formations = this.formations.map((f) =>
f.id === id ? { ...f, placesRestantes: f.placesRestantes - 1 } : f,
);
// Lire les champs d'un formulaire réactif
const { nom, email } = this.formulaire.getRawValue();
// Envoyer à une API une copie des données, complétée
const aEnvoyer = { ...this.formulaire.getRawValue(), dateInscription: new Date().toISOString() };
// Déstructurer la réponse d'une API
const { data, total } = reponse;
-
Activité 1 : extraire les données d'une commande
- Dans le dossier
rappel-ecmascript(dépôt GitHub), créer le fichier19-commande.js. À partir de l'objetreponseci-dessous, écrire une seule instruction de déstructuration qui crée les variables :numero;nomClient(le nom du client, renommé) etville(absente : valeur par défaut'Tunis') ;premierProduit(le produit du premier article) etautresArticles(le reste des articles) ;livraison(absente : valeur par défaut'standard').
- Dans le dossier
const reponse = {
numero: 'CMD-2026-0142',
date: '2026-10-29',
client: { nom: 'Amira Ben Ali', email: 'amira@exemple.tn' },
articles: [
{ produit: 'Clavier', prix: 45 },
{ produit: 'Souris', prix: 20 },
{ produit: 'Tapis', prix: 8.5 },
],
};
// votre déstructuration ici
console.log(`${numero} – ${nomClient} (${ville})`);
console.log(`Premier article : ${premierProduit}, puis ${autresArticles.length} autre(s)`);
console.log(`Livraison : ${livraison}`);
// Résultat attendu :
// CMD-2026-0142 – Amira Ben Ali (Tunis)
// Premier article : Clavier, puis 2 autre(s)
// Livraison : standard
-
Activité 2 : une fonction à options
- Dans
20-alerte.js, écrire la fonction fléchéecreerAlertequi reçoit un objet d'options déstructuré, avec les valeurs par défautmessage = 'Opération terminée',type = 'info'etfermable = true, et qui peut être appelée sans argument. Elle renvoie le HTML d'une alerte Bootstrap ; la classealert-dismissiblen'est ajoutée que sifermablevauttrue.
- Dans
console.log(creerAlerte());
console.log(creerAlerte({ message: 'Enregistré !', type: 'success' }));
console.log(creerAlerte({ type: 'danger', fermable: false, message: 'Erreur' }));
// Résultat attendu :
// <div class="alert alert-info alert-dismissible">Opération terminée</div>
// <div class="alert alert-success alert-dismissible">Enregistré !</div>
// <div class="alert alert-danger">Erreur</div>
- Que se passe-t-il si on retire
= {}à la fin des paramètres et qu'on appellecreerAlerte()? Noter le message en commentaire.
Activité 3 : un panier immuable
- Dans
21-panier.js, commencer le fichier par'use strict';, puis écrire quatre fonctions fléchées qui ne modifient jamais le panier reçu :ajouterArticle(panier, article)→ nouveau panier avec l'article à la fin ;changerQuantite(panier, id, quantite)→ nouveau panier où l'article a la nouvelle quantité ;retirerArticle(panier, id)→ nouveau panier sans l'article ;total(panier)→ montant total, en déstructurantprixetquantitedans le callback dereduce.
- Le panier de départ est gelé avec
Object.freeze(): toute tentative de modification provoque une erreur et révèle une fonction mal écrite.
const panier0 = Object.freeze([
Object.freeze({ id: 1, nom: 'Clavier', prix: 45, quantite: 1 }),
Object.freeze({ id: 2, nom: 'Souris', prix: 20, quantite: 2 }),
]);
const panier1 = ajouterArticle(panier0, Object.freeze({ id: 3, nom: 'Écran', prix: 420, quantite: 1 }));
const panier2 = changerQuantite(panier1, 2, 3);
const panier3 = retirerArticle(panier2, 1);
console.log(total(panier0).toFixed(3), total(panier1).toFixed(3), total(panier2).toFixed(3), total(panier3).toFixed(3));
console.log(panier0.length, panier3.length, panier2[0] === panier0[0], panier2[1] === panier1[1]);
// Résultat attendu :
// 85.000 505.000 525.000 480.000
// 2 2 true false
- Expliquer en commentaire la dernière ligne : pourquoi
panier2[0] === panier0[0]vaut-iltrue, etpanier2[1] === panier1[1]vaut-ilfalse? - Écrire volontairement une version de
ajouterArticleavecpanier.push(article): quelle erreur obtenez-vous ? (TypeError: Cannot add property 2, object is not extensible)
Activité 4 : fusionner une configuration
- Dans
22-configuration.js, fusionner la configuration par défaut et celle de l'utilisateur avec spread :
const configDefaut = { langue: 'fr', pagination: 10, theme: { mode: 'clair', couleur: 'primary' } };
const configUtilisateur = { pagination: 25, theme: { mode: 'sombre' } };
- Écrire d'abord la fusion simple
{ ...configDefaut, ...configUtilisateur }et afficher sa propriététheme: la couleur a disparu. Expliquer pourquoi en commentaire. - Corriger la fusion pour obtenir le résultat ci-dessous, puis vérifier que
configDefaut.theme.modevaut toujours'clair'.
{
langue: 'fr',
pagination: 25,
theme: { mode: 'sombre', couleur: 'primary' }
}
-
Activité 5 : le piège de la copie superficielle
- Dans
23-copie.js, créer un objetgroupecontenant un nom, un tableaustagiaireset un objetformateur. En faire trois copies :copie1avec spread ;copie2avecstructuredClone();copie3avec spread, en copiant aussi le tableaustagiairesavec spread.
- Dans chaque copie, ajouter un stagiaire et changer le nom du formateur. Afficher ensuite l'objet
grouped'origine après chaque essai et compléter le tableau suivant dans un commentaire :
- Dans
| Copie | Le tableau d'origine a-t-il changé ? | Le formateur d'origine a-t-il changé ? |
|---|---|---|
copie1 (spread) |
? | ? |
copie2 (structuredClone) |
? | ? |
copie3 (spread + spread du tableau) |
? | ? |
- Faire un commit après chaque activité, puis un push :
git commit -m "Destructuration, spread et rest : activité N".
Erreurs fréquentes
| Message ou symptôme | Cause | Solution |
|---|---|---|
TypeError: Cannot destructure property 'x' of 'undefined' as it is undefined. |
Déstructuration d'une valeur absente (souvent un argument oublié) | Prévoir une valeur par défaut : ({ x } = {}) => … |
TypeError: Cannot destructure property 'x' of 'null' as it is null. |
La donnée vaut null (réponse d'API vide, par exemple) |
Vérifier la donnée avant, ou écrire const { x } = donnee ?? {}; |
TypeError: undefined is not iterable |
Déstructuration de tableau, ou spread d'un tableau absent ([...x], f(...x)) ; le spread d'objet { ...x }, lui, ignore undefined |
Vérifier la donnée, ou utiliser donnee ?? [] |
SyntaxError: Rest element must be last element |
...reste placé avant d'autres variables |
Placer le rest en dernière position |
SyntaxError: Rest parameter must be last formal parameter |
Paramètre rest suivi d'un autre paramètre | Placer le paramètre rest en dernier |
ReferenceError: adresse is not defined après { adresse: { ville } } |
Seule la variable finale (ville) est créée |
Déstructurer aussi adresse séparément si nécessaire |
| L'original est modifié alors qu'on a travaillé sur une copie | Copie superficielle : objet ou tableau imbriqué partagé | Étaler aussi le niveau imbriqué, ou utiliser structuredClone() |
Une propriété n'est pas mise à jour dans { ...a, ...b } |
Ordre d'écriture : la dernière valeur l'emporte | Écrire en dernier l'objet dont les valeurs doivent gagner |
-
À retenir
- Déstructuration d'objet: par le nom des propriétés, avec renommage (
a: b) et valeur par défaut (a = valeur). - Déstructuration de tableau: par la position ; une virgule seule saute un élément.
- Spread
...déplie un tableau ou un objet : copier, fusionner, compléter, passer des arguments. - Rest
...regroupe ce qui reste, toujours en dernière position : paramètres d'une fonction ou déstructuration. - Spread fait une copie superficielle;
structuredClone()fait une copie profonde. - Dans Angular, on met à jour les données en créant de nouvelles versions :
[...liste, x],mapavec{ ...element },filter.
- Déstructuration d'objet: par le nom des propriétés, avec renommage (
Trois petits points, deux rôles : à droite du = ils déplient, à gauche ils regroupent. Et pour changer une donnée dans Angular, on n'écrit pas dessus : on en crée une nouvelle version.