27 avril 2023

L'affectation par décomposition

Les deux structures de données les plus utilisées en JavaScript sont Object et Array.

  • Les objets nous permettent de crĂ©er une seule entitĂ© qui stocke les Ă©lĂ©ments de donnĂ©es par clĂ©.
  • Les tableaux nous permettent de rassembler des Ă©lĂ©ments de donnĂ©es dans une liste ordonnĂ©e.

Mais lorsque nous transmettons ceux-ci à une fonction, il se peut que celle-ci n’ait pas besoin d’un objet / tableau dans son ensemble, mais plutît de morceaux individuels.

L’affectation par dĂ©composition est une syntaxe spĂ©ciale qui nous permet de “dĂ©compresser” des tableaux ou des objets dans un ensemble de variables, ce qui est parfois plus pratique.

La décomposition fonctionne également trÚs bien avec des fonctions complexes comportant de nombreux paramÚtres, valeurs par défaut, etc.

DĂ©composition d’un tableau

Voici un exemple de la façon dont un tableau est décomposé en variables :

// nous avons un tableau avec le prénom et le nom
let arr = ["John", "Smith"]

// l'affectation par décomposition
// sets firstName = arr[0]
// and surname = arr[1]
let [firstName, surname] = arr;

alert(firstName); // John
alert(surname);  // Smith

Maintenant, nous pouvons travailler avec des variables plutĂŽt que des parties du tableau.

Cela a l’air pas mal quand c’est combinĂ© avec split ou tout autre mĂ©thode de renvoi de tableau :

let [firstName, surname] = "John Smith".split(' ');
alert(firstName); // John
alert(surname);  // Smith

Comme vous pouvez le voir, la syntaxe est simple. Il y a cependant plusieurs dĂ©tails particuliers. Voyons plus d’exemples, pour mieux le comprendre.

“DĂ©composition” ne veut pas dire “destruction”.

Cette manipulation est appelĂ©e “affectation par dĂ©composition”, car elle “se dĂ©compose” en copiant ses Ă©lĂ©ments dans des variables. Mais le tableau lui-mĂȘme n’est pas modifiĂ©.

C’est juste une façon plus courte d’écrire :

// let [firstName, surname] = arr;
let firstName = arr[0];
let surname = arr[1];
Ignorer les éléments en utilisant des virgules

Les Ă©lĂ©ments indĂ©sirables du tableau peuvent Ă©galement ĂȘtre supprimĂ©s via une virgule supplĂ©mentaire :

// second element is not needed
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert( title ); // Consul

Dans le code ci-dessus, le deuxiĂšme Ă©lĂ©ment du tableau est ignorĂ©, le troisiĂšme est attribuĂ© Ă  title et le reste du tableau est Ă©galement ignorĂ© (car il n’y a pas de variables pour eux).

Fonctionne avec n’importe quel itĂ©rable Ă  droite


En fait, nous pouvons l’utiliser avec n’importe quel itĂ©rable, pas seulement les tableaux :

let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);

That works, because internally a destructuring assignment works by iterating over the right value. It’s a kind of syntax sugar for calling for..of over the value to the right of = and assigning the values.

Attribuer n’importe quoi à la partie gauche

Nous pouvons utiliser n’importe quels “assignables” à gauche.

Par exemple, une propriĂ©tĂ© d’objet :

let user = {};
[user.name, user.surname] = "John Smith".split(' ');

alert(user.name); // John
alert(user.surname); // Smith
Boucler avec .entries()

Dans le chapitre précédent, nous avons vu la méthode Object.entries(obj).

Nous pouvons l’utiliser avec la dĂ©composition pour boucler sur les clĂ©s et valeurs d’un objet :

let user = {
  name: "John",
  age: 30
};

// boucler sur les clés et les valeurs
for (let [key, value] of Object.entries(user)) {
  alert(`${key}:${value}`); // name:John, ensuite age:30
}

Le code similaire pour un Map est plus simple, car il est itérable :

let user = new Map();
user.set("name", "John");
user.set("age", "30");

// Map est itéré sous forme de paires [key, value], ce qui est trÚs pratique pour la déstructuration
for (let [key, value] of user) {
  alert(`${key}:${value}`); // name:John, ensuite age:30
}
Astuce d’échange de variables

Il existe une astuce bien connue pour permuter les valeurs de deux variables Ă  l’aide d’une affectation de dĂ©structuration :

let guest = "Jane";
let admin = "Pete";

// Permutons les valeurs : guest=Pete, admin=Jane
[guest, admin] = [admin, guest];

alert(`${guest} ${admin}`); // Pete Jane (échangé avec succÚs !)

Ici, nous crĂ©ons un tableau temporaire de deux variables et le dĂ©structurons immĂ©diatement dans l’ordre permutĂ©.

Nous pouvons échanger plus de deux variables de cette façon.

Le rest ‘
’

Habituellement, si le tableau est plus long que la liste Ă  gauche, les Ă©lĂ©ments “supplĂ©mentaires” sont omis.

Par exemple, ici, seuls deux éléments sont pris et le reste est simplement ignoré :

let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert(name1); // Julius
alert(name2); // Caesar
// Les autres éléments ne sont affectés nulle part

Si nous souhaitons Ă©galement rassembler tout ce qui suit, nous pouvons ajouter un paramĂštre supplĂ©mentaire qui permet d’obtenir “le reste” Ă  l’aide de trois points "..." :

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

// reste est un tableau d'éléments, à partir du 3Úme
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2

La valeur de rest est le tableau des éléments du tableau restants.

Nous pouvons utiliser n’importe quel autre nom de variable Ă  la place de rest, assurez-vous simplement qu’il a trois points devant lui et soit placĂ© en dernier dans l’affectation par dĂ©composition.

let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// maintenant titles = ["Consul", "of the Roman Republic"]

Les valeurs par défaut

Si le tableau est plus court que la liste des variables Ă  gauche, il n’y aura aucune erreur. Les valeurs absentes sont considĂ©rĂ©es comme non dĂ©finies :

let [firstName, surname] = [];

alert(firstName); // undefined
alert(surname); // undefined

Si nous voulons qu’une valeur “par dĂ©faut” remplace la valeur manquante, nous pouvons la fournir en utilisant = :

// les valeurs par défaut
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

alert(name);    // Julius (depuis le tableau)
alert(surname); // Anonymous (valeur par défaut)

Les valeurs par dĂ©faut peuvent ĂȘtre des expressions plus complexes ou mĂȘme des appels de fonction. Ils ne sont Ă©valuĂ©s que si la valeur n’est pas fournie.

Par exemple, nous utilisons ici la fonction prompt pour deux valeurs par défaut :

// ne demande que le nom de famille
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];

alert(name);    // Julius (depuis le tableau)
alert(surname); // saisi par l'utilisateur

Notez que le prompt ne s’exĂ©cutera que pour la valeur manquante (surname).

DĂ©composition d’object

L’affectation par dĂ©composition fonctionne Ă©galement avec les objets.

La syntaxe de base est la suivante :

let {var1, var2} = {var1:
, var2:
}

Nous devrions avoir un objet existant sur le cĂŽtĂ© droit, que nous voulons diviser en variables. La partie gauche contient un “modĂšle” de type objet pour les propriĂ©tĂ©s correspondantes. Dans le cas le plus simple, c’est une liste de noms de variables dans {...}.

Par exemple :

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

let {title, width, height} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

Les propriétés options.title, options.width et options.height sont affectées aux variables correspondantes.

L’ordre n’a pas d’importance. Cela fonctionne aussi :

// changé l'ordre dans let {...}
let {height, width, title} = { title: "Menu", height: 200, width: 100 }

Le pattern Ă  gauche peut ĂȘtre plus complexe et spĂ©cifier le mapping entre propriĂ©tĂ©s et variables.

Si nous voulons affecter une propriété à une variable portant un autre nom, par exemple, options.width pour aller dans la variable nommée w, alors nous pouvons la définir en utilisant deux points :

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;

// width -> w
// height -> h
// title -> title

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

Les deux points montrent “quoi: va oĂč”. Dans l’exemple ci-dessus, la propriĂ©tĂ© width est dĂ©finie sur w, la propriĂ©tĂ© height est dĂ©finie sur h et le title est attribuĂ© au mĂȘme nom.

Pour les propriĂ©tĂ©s potentiellement manquantes, nous pouvons dĂ©finir les valeurs par dĂ©faut Ă  l’aide de "=", comme ceci :

let options = {
  title: "Menu"
};

let {width = 100, height = 200, title} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

Comme pour les tableaux ou les paramĂštres de fonction, les valeurs par dĂ©faut peuvent ĂȘtre des expressions ou mĂȘme des appels de fonction. Elles seront Ă©valuĂ©es si la valeur n’est pas fournie.

Le code prompt ci-dessous demande la width, mais pas le title.

let options = {
  title: "Menu"
};

let {width = prompt("width?"), title = prompt("title?")} = options;

alert(title);  // Menu
alert(width);  // saisi par l'utilisateur

Nous pouvons Ă©galement combiner les deux points et l’égalitĂ© :

let options = {
  title: "Menu"
};

let {width: w = 100, height: h = 200, title} = options;

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

Si nous avons un objet complexe avec de nombreuses propriétés, nous pouvons extraire que ce dont nous avons besoin :

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

// extraire uniquement le titre en tant que variable
let { title } = options;

alert(title); // Menu

Le pattern rest “
”

Et si l’objet a plus de propriĂ©tĂ©s que de variables ? Peut-on en prendre puis assigner le “rest” quelque part ?

Nous pouvons utiliser le modùle rest, comme nous l’avons fait avec les tableaux. Il n’est pas pris en charge par certains navigateurs plus anciens (IE, utilisez Babel pour le polyfiller), mais fonctionne avec les modernes.

Cela ressemble Ă  ceci :

let options = {
  title: "Menu",
  height: 200,
  width: 100
};

// title = propriété nommée title
// rest = objet avec le reste des propriétés
let {title, ...rest} = options;

// maintenant title="Menu", rest={height: 200, width: 100}
alert(rest.height);  // 200
alert(rest.width);   // 100
Attention, sans let, ça coince

Dans les exemples ci-dessus, les variables ont Ă©tĂ© dĂ©clarĂ©es juste avant l’affectation : let {
} = {
}. Bien sĂ»r, nous pourrions aussi utiliser des variables existantes. Mais il y a un problĂšme.

Cela ne fonctionnera pas :

let title, width, height;

// erreur dans cette ligne
{title, width, height} = {title: "Menu", width: 200, height: 100};

Le problĂšme est que JavaScript traite {...} dans le flux de code principal (pas dans une autre expression) en tant que bloc de code. De tels blocs de code peuvent ĂȘtre utilisĂ©s pour regrouper des instructions, comme ceci :

{
  // un bloc de code
  let message = "Hello";
  // ...
  alert( message );
}

Donc ici, JavaScript suppose que nous avons un bloc de code, c’est pourquoi il y a une erreur. Nous voulons plutĂŽt la dĂ©structuration.

Pour montrer à JavaScript qu’il ne s’agit pas d’un bloc de code, nous pouvons envelopper l’expression entre parenthùses (...) :

let title, width, height;

// maintenant c'est bon
({title, width, height} = {title: "Menu", width: 200, height: 100});

alert( title ); // Menu

Décomposition imbriquée

Si un objet ou un tableau contient d’autres objets et tableaux imbriquĂ©s, nous pouvons utiliser des modĂšles Ă  gauche plus complexes pour extraire des parties plus profondes.

Dans le code ci-dessous, options a un autre objet dans la propriĂ©tĂ© size et un tableau dans la propriĂ©tĂ© items. Le modĂšle Ă  gauche de l’affectation a la mĂȘme structure pour en extraire des valeurs :

let options = {
  size: {
    width: 100,
    height: 200
  },
  items: ["Cake", "Donut"],
  extra: true
};

// affectation par décomposition divisée sur plusieurs lignes pour la clarté
let {
  size: { // mettre la taille ici
    width,
    height
  },
  items: [item1, item2], // attribuer des éléments ici
  title = "Menu" // non présent dans l'objet (la valeur par défaut est utilisée)
} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200
alert(item1);  // Cake
alert(item2);  // Donut

Toutes les propriĂ©tĂ©s de l’objet options, Ă  l’exception de extra qui est absente dans la partie gauche, sont affectĂ©s aux variables correspondantes :

Finnalement nous avons width, height, item1, item2 et title à partir de la valeur par défaut.

Notez qu’il n’y a pas de variables pour size et items, car nous prenons leur contenu à la place.

ParamĂštres de fonction intelligente

Il peut arriver qu’une fonction ait plusieurs paramĂštres, dont la plupart sont facultatifs. C’est particuliĂšrement vrai pour les interfaces utilisateur. Imaginez une fonction qui crĂ©e un menu. Il peut avoir une largeur, une hauteur, un titre, une liste d’articles, etc.

Voici une mauvaise façon d’écrire ce genre de fonction :

function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
  // ...
}

Dans la vraie vie, le problĂšme est de savoir comment retenir l’ordre des arguments. Habituellement, les IDE essaient de nous aider, surtout si le code est bien documentĂ©, mais quand mĂȘme
 Un autre problĂšme est de savoir comment appeler une fonction lorsque la plupart des paramĂštres sont corrects par dĂ©faut.

Comme ceci ?

// undefined oĂč les valeurs par dĂ©faut sont adĂ©quates
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])

C’est moche. Et devient illisible lorsque nous traitons plus de paramùtres.

La décomposition vient à la rescousse !

Nous pouvons passer des paramĂštres sous forme d’objet, et la fonction les dĂ©composent immĂ©diatement en variables :

// on passe un objet Ă  fonction
let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

// ...et il est immédiatement étendu aux variables
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
  // title, items – pris des options,
  // width, height – dĂ©fauts utilisĂ©s
  alert( `${title} ${width} ${height}` ); // My Menu 200 100
  alert( items ); // Item1, Item2
}

showMenu(options);

Nous pouvons également utiliser une décomposition plus complexe avec des objets imbriqués et des mappings de deux points :

let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

function showMenu({
  title = "Untitled",
  width: w = 100,  // width va Ă  w
  height: h = 200, // height va Ă  h
  items: [item1, item2] // items premier élément va à item1, deuxiÚme à item2
}) {
  alert( `${title} ${w} ${h}` ); // My Menu 100 200
  alert( item1 ); // Item1
  alert( item2 ); // Item2
}

showMenu(options);

La syntaxe complĂšte est la mĂȘme que pour une affectation par dĂ©composition :

function({
  incomingProperty: varName = defaultValue
  ...
})

Ensuite, pour un objet de paramÚtres, il y aura une variable varName pour la propriété incomingProperty, avec defaultValue par défaut.

Veuillez noter qu’une telle dĂ©structuration suppose que showMenu() a un argument. Si nous voulons toutes les valeurs par dĂ©faut, alors nous devrions spĂ©cifier un objet vide :

showMenu({}); // ok, toutes les valeurs par défaut

showMenu(); // cela donnerait une erreur

Nous pouvons rĂ©soudre ce problĂšme en faisant de {} la valeur par dĂ©faut pour l’objet entier de paramĂštres :

function showMenu({ title = "Menu", width = 100, height = 200 } = {}) {
  alert( `${title} ${width} ${height}` );
}

showMenu(); // Menu 100 200

Dans le code ci-dessus, la totalité des arguments objet est {} par défaut, il y a donc toujours quelque chose à décomposer.

Résumé

  • L’affectation par dĂ©composition permet de mapper instantanĂ©ment un objet ou un tableau sur de nombreuses variables.

  • La syntaxe complĂšte de l’objet :

    let {prop : varName = default, ...rest} = object

    Cela signifie que la propriĂ©tĂ© prop doit aller dans la variable varName et que, si aucune propriĂ©tĂ© de ce type n’existe, la valeur default doit ĂȘtre utilisĂ©e.

    Les propriĂ©tĂ©s d’objet sans mappage sont copiĂ©es dans l’objet rest.

  • La syntaxe complĂšte du tableau :

    let [item1 = default, item2, ...rest] = array

    Le premier item va Ă  item1; le second passe Ă  item2, tout le reste du tableau correspond au rest.

  • Il est possible d’extraire des donnĂ©es de tableaux / objets imbriquĂ©s, pour cela le cĂŽtĂ© gauche doit avoir la mĂȘme structure que le droit.

Exercices

importance: 5

Nous avons un objet :

let user = {
  name: "John",
  years: 30
};

Écrivez l’affectation par dĂ©composition qui se lit comme suit :

  • La propriĂ©tĂ© name dans la variable name.
  • La propriĂ©tĂ© years dans la variable age.
  • La propriĂ©tĂ© isAdmin dans la variable isAdmin (false si absent)

Voici un exemple des valeurs aprĂšs votre affectation :

let user = { name: "John", years: 30 };

// votre code Ă  gauche ::
// ... = user

alert( name ); // John
alert( age ); // 30
alert( isAdmin ); // false
let user = {
  name: "John",
  years: 30
};

let {name, years: age, isAdmin = false} = user;

alert( name ); // John
alert( age ); // 30
alert( isAdmin ); // false
importance: 5

Il y a un objet salaries :

let salaries = {
  "John": 100,
  "Pete": 300,
  "Mary": 250
};

Créer la fonction topSalary(salaries) qui renvoie le nom de la personne la mieux payée.

  • Si salaries est vide, devrait retourner null.
  • S’il y a plusieurs personnes les mieux payĂ©es, renvoyez-en une.

P.S. Utilisez Object.entries et la décomposition pour parcourir les paires clé / valeur.

Open a sandbox with tests.

function topSalary(salaries) {

  let maxSalary = 0;
  let maxName = null;

  for(const [name, salary] of Object.entries(salaries)) {
    if (maxSalary < salary) {
      maxSalary = salary;
      maxName = name;
    }
  }

  return maxName;
}

Ouvrez la solution avec des tests dans une sandbox.

Carte du tutoriel

Commentaires

lire ceci avant de commenter

  • Si vous avez des amĂ©liorations Ă  suggĂ©rer, merci de soumettre une issue GitHub ou une pull request au lieu de commenter.
  • Si vous ne comprenez pas quelque chose dans l'article, merci de prĂ©ciser.
  • Pour insĂ©rer quelques bouts de code, utilisez la balise <code>, pour plusieurs lignes – enveloppez-les avec la balise <pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen
)