Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
Angular

Destructuration, opérateurs spread et rest

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. ...

0 chapitres 0 QCM Accès gratuit
Dans cet article

À 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.
  1. 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
  2. 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.
  3. 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.
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

La déstructuration d'un objet

  • 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) : ...reste regroupe 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.

    La déstructuration d'un tableau

    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 de split()…
    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
    1. 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);
    }
    1. 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.

    Les opérateurs spread et rest

    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' ]
    1. L'opérateur spread sur les objets

      • Placé dans un objet littéral { }, ...objet recopie toutes ses propriétés. Si une propriété apparaît plusieurs fois, la dernière l'emporte: l'ordre d'écriture compte.
    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 }
    1. 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.

    Copie superficielle et copie profonde d'un objet

    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
    1. L'opérateur rest dans les paramètres

      • Dans la liste des paramètres d'une fonction, ...nom regroupe tous les arguments restants dans un vrai tableau. Il remplace l'ancien objet arguments, qui n'existe pas dans les fonctions fléchées.
      • Le paramètre rest est toujours le dernier.
    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
    1. Mettre à jour sans modifier : l'immutabilité

      • Être immutable, c'est ne jamais modifier une donnée existante, mais créer une nouvelle version. Spread, map et filter le permettent pour les trois opérations courantes sur une liste :
    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;
    1. Activité 1 : extraire les données d'une commande

      • Dans le dossier rappel-ecmascript (dépôt GitHub), créer le fichier 19-commande.js. À partir de l'objet reponse ci-dessous, écrire une seule instruction de déstructuration qui crée les variables :
        • numero;
        • nomClient (le nom du client, renommé) et ville (absente : valeur par défaut 'Tunis') ;
        • premierProduit (le produit du premier article) et autresArticles (le reste des articles) ;
        • livraison (absente : valeur par défaut 'standard').
    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
    1. Activité 2 : une fonction à options

      • Dans 20-alerte.js, écrire la fonction fléchée creerAlerte qui reçoit un objet d'options déstructuré, avec les valeurs par défaut message = 'Opération terminée', type = 'info' et fermable = true, et qui peut être appelée sans argument. Elle renvoie le HTML d'une alerte Bootstrap ; la classe alert-dismissible n'est ajoutée que si fermable vaut true.
    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 appelle creerAlerte()? 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éstructurant prix et quantite dans le callback de reduce.
    • 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-il true, et panier2[1] === panier1[1] vaut-il false?
    • Écrire volontairement une version de ajouterArticle avec panier.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.mode vaut toujours 'clair'.
    {
      langue: 'fr',
      pagination: 25,
      theme: { mode: 'sombre', couleur: 'primary' }
    }
    1. Activité 5 : le piège de la copie superficielle

      • Dans 23-copie.js, créer un objet groupe contenant un nom, un tableau stagiaires et un objet formateur. En faire trois copies :
        • copie1 avec spread ;
        • copie2 avec structuredClone();
        • copie3 avec spread, en copiant aussi le tableau stagiaires avec spread.
      • Dans chaque copie, ajouter un stagiaire et changer le nom du formateur. Afficher ensuite l'objet groupe d'origine après chaque essai et compléter le tableau suivant dans un commentaire :
    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
    1. À 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], map avec { ...element }, filter.
    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.