ES6 en détails est une série d’articles décrivant les nouvelles fonctionnalités ajoutées au langage de programmation JavaScript avec la sixième édition du standard ECMAScript (ES6 en abrégé).

Note de l’éditeur : une version du billet d’aujourdhui, écrit par Nick Fitzgerald, ingénieur travaillant sur les outils de développement Firefox, avait précédemment été publiée sur son blog : L’affectation par décomposition dans ES6.

Qu’est-ce que l’affectation par décomposition ?

L’affectation par décomposition permet d’affecter des propriétés d’un tableau ou d’un objet à des variables en utilisant une syntaxe semblable aux littéraux de tableaux ou aux littéraux objets. Cette syntaxe peut s’avérer extrêmement laconique et plus claire que la syntaxe avec laquelle on accède habituellement aux propriétés.

Sans utiliser l’affectation par décomposition, on pourrait accéder aux trois premiers éléments d’un tableau de cette façon :

var premier = unTableau[0];
var deuxième = unTableau[1];
var troisième = unTableau[2];

Grâce à l’affectation par décomposition, on obtient un code équivalent, beaucoup plus lisible et concis :

var [premier, deuxième, troisième] = unTableau;

SpiderMonkey (le moteur JavaScript de Firefox) supporte d’ores et déjà la plupart des aspects liés à la décomposition même s’il reste encore quelques éléments à couvrir. Le support de SpiderMonkey pour la décomposition (et ES6 en général) est suivi avec le bogue 694100.

Décomposer les tableaux et les itérables

Juste avant, nous avons déjà vu un exemple d’affectation par décomposition sur un tableau. La forme générale de la syntaxe est :

[ variable1, variable2, ..., variableN ] = tableau;

Cela affectera les éléments respectifs du tableau tableau aux variables variable1 à variableN. Si vous souhaitez déclarer vos variables en même temps, vous pouvez ajouter un var, let ou un const devant l’affectation.

var [ variable1, variable2, ..., variableN ] = tableau;
let [ variable1, variable2, ..., variableN ] = tableau;
const [ variable1, variable2, ..., variableN ] = tableau;

De fait, le terme « variable » est un peu usurpé ici car il est possible d’imbriquer des structures comme on le souhaite :

var [toto, [[truc], machin]] = [1, [[2], 3]];
console.log(toto);
// 1
console.log(truc);
// 2
console.log(machin);
// 3

De plus, on peut sauter des éléments du tableau qui est décomposé :

var [,,troisième] = ["toto", "truc", "machin"];
console.log(troisième);
// "machin"

Et il est possible de capturer les éléments en fin de tableau avec un paramètre du reste :

var [tête, ...queue] = [1, 2, 3, 4];
console.log(queue);
// [2, 3, 4]

Lorsque vous accédez aux éléments d’un tableau qui n’existent pas ou qui se situent hors de ses limites, vous obtenez le même résultat qu’avec un accès via l’indice correspondant : undefined.

console.log([][0]);
// undefined

var [manquant] = [];
console.log(manquant);
// undefined

On notera que l’affectation par décomposition pour un tableau fonctionne également avec n’importe quel itérable :

function* fibs() {
  var a = 0;
  var b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

var [premier, deuxième, troisième, quatrième, cinquième, sixième] = fibs();
console.log(sixième);
// 5

Décomposer des objets

La décomposition des objets permet de lier des variables à partir des différentes propriétés d’un objet. Vous définissez les propriétés que vous souhaitez lier, suivies des variables auxquelles lier leurs valeurs.

var robotA = { nom: "Bender" };
var robotB = { nom: "Flexo" };

var { nom: nomA } = robotA;
var { nom: nomB } = robotB;

console.log(nomA);
// "Bender"
console.log(nomB);
// "Flexo"

Cela représente un raccourci syntaxique utile lorsque les noms des propriétés et les noms des variables sont les mêmes :

var { toto, truc } = { toto: "lorem", truc: "ipsum" };
console.log(toto);
// "lorem"
console.log(truc);
// "ipsum"

De la même façon qu’avec la décomposition des tableaux, il est possible d’imbriquer et de combiner des motifs pour aller plus loin dans la décomposition :

var objetCompliqué = {
  propTableau: [
    "Zapp",
    { second: "Brannigan" }
  ]
};

var { propTableau: [premier, { second }] } = objetCompliqué;

console.log(premier);
// "Zapp"
console.log(second);
// "Brannigan"

Si vous décomposez des propriétés non définies, vous obtiendrez undefined :

var { manquante } = {};
console.log(manquante);
// undefined

Un des pièges potentiels qu’il faut retenir : ne pas oublier de déclarer les variables qu’on utilise avec la décomposition d’un objet (c’est-à-dire ne pas oublier d’utiliser let, ni const, ni var) :

{ bombe } = { bombe: 10 };
//  Erreur de syntaxe

Cela se produit car la grammaire JavaScript indique au moteur que toute instruction commençant par un { doit être comprise comme un bloc d’instruction (par exemple, { console } représente une instruction de bloc valide). La solution est d’entourer toute l’expression avec des parenthèses :

({ toto } = {});
// Pas d'erreur

Décomposer des valeurs qui ne sont pas des objets, des tableaux ou des itérables

Si vous essayez d’utiliser la décomposition sur null ou undefined, vous obtiendrez une exception TypeError :

var [bombe] = null;
// TypeError: null has no properties

Cependant, il est possible de décomposer d’autres types primitifs tels que les booléens, les nombres, les chaînes de caractères et d’obtenir… undefined :

var [wtf] = NaN;
console.log(wtf);
// undefined

Cela peut sembler incongru mais si on y regarde de plus près, la clé de cette énigme est plutôt simple. Lorsqu’on utilise la décomposition d’un objet, la valeur décomposée doit nécessairement être convertible en un objet. La plupart des types peuvent être convertis en un objet mais null et undefined ne peuvent pas être convertis. Lorsqu’on utilise la décomposition d’un tableau, la valeur décomposée doit avoir un itérateur (propriété Symbol.iterator).

Valeurs par défaut

Il est aussi possible de fournir des valeurs par défaut pour parer au cas où la propriété décomposée n’est pas définie :

var [manquante = true] = [];
console.log(manquante;
// true

var { message: msg = "Quelque chose cloche" } = {};
console.log(msg);
// "Quelque chose cloche"

var { x = 3 } = {};
console.log(x);
// 3

Note de l’éditeur : cette fonctionnalité est actuellement implémentée dans Firefox pour les deux premiers cas, le troisième n’est pas encore géré. Pour plus d’informations, voir le bogue 932080.

Applications pratiques pour la décomposition

Définitions des paramètres d’une fonction

En tant que développeurs, nous pouvons souvent offrir une API plus ergonomique en acceptant un unique objet avec plusieurs propriétés plutôt qu’une suite de paramètres individuels dont il faut mémoriser l’ordre pour pouvoir utiliser l’API. La décomposition peut nous aider pour éviter de répéter ce paramètre unique à chaque fois qu’on souhaite faire référence à une de ses propriétés :

function removeBreakpoint({ url, line, column }) {
  // ...
}

Ceci est un fragment de code simplifié, tiré d’un code réel, celui du débogueur JavaScript des outils de développement Firefox (qui est également implémenté en JavaScript —yo dawg). Nous avons trouvé que ce concept était particulièrement agréable à manipuler.

Paramètres d’un objet de configuration

En partant de l’exemple précédent, nous pouvons également fournir des valeurs par défaut pour les objets que nous décomposons. Cela s’avère particulièrement utile lorsqu’on dispose d’un objet utilisé pour fournir une configuration et que la plupart des propriétés de cet objet possèdent des valeurs par défaut raisonnables. La fonction jQuery ajax, par exemple, utilise un objet de configuration comme second paramètre. Elle pourrait être réécrite comme ceci :

jQuery.ajax = function (url, {
  async = true,
  beforeSend = noop,
  cache = true,
  complete = noop,
  crossDomain = false,
  global = true,
  // ... d'autres éléments de config
}) {
  // ... faire quelque chose
};

Cela permet d’éviter d’avoir à répéter var toto = config.toto || defautPourToto; pour chacune des propriétés de l’objet de configuration

Note de l’éditeur : malheureusement, les valeurs par défaut pour la syntaxe raccourcie appliquée aux objets n’est toujours pas implémentée dans Firefox. Je sais, depuis la note précédente, cela fait plusieurs paragraphes que nous avons eus pour travailler dessus. Voir le bogue 932080 pour les informations les plus récentes à ce sujet.

Décomposition et protocole d’itération ES6

ECMAScript 6 définit également un protocole d’itération, que nous avons évoqué précédemment dans cette série d’articles. Lorsque vous parcourez des objets Map (un ajout à la bibliothèque native ES6), vous obtenez une série de paires [clé, valeur]. Cette paire peut être décomposée pour accéder facilement à la clé d’une part et à la valeur d’autre part.

var map = new Map();
map.set(window, "globale");
map.set(document, "le document");

for (var [clé, valeur] of map) {
  console.log(clé + " est " + valeur);
}
// "[object Window] est globale"
// "[object HTMLDocument] est le document"

Parcourir les clés uniquement !
for (var [clé] of map) {
  // ...
}

Parcourir uniquement les valeurs
for (var [,valeur] of map) {
  // ...
}

Renvoyer plusieurs valeurs

Les valeurs de retour multiples ne font pas vraiment partie du langage mais ce n’est pas nécessaire car vous pouvez renvoyer un tableau et décomposer le résultat :

function renvoyerPlusieursValeurs() {
  return [1, 2];
}
var [toto, truc] = renvoyerPlusieursValeurs();

Vous pouvez aussi utiliser un objet qui agit comme un conteneur et qui nomme les valeurs renvoyées :

function renvoyerPlusieursValeurs() {
  return {
    toto: 1,
    truc: 2
  };
}
var { toto, truc } = renvoyerPlusieursValeurs();

Ces deux constructions sont beaucoup plus utiles qu’un conteneur temporaire :

function renvoyerPlusieursValeurs() {
  return {
    toto: 1,
    truc: 2
  };
}
var temp = renvoyerPlusieursValeurs();
var toto = temp.toto;
var truc = temp.truc;

Elles sont également mieux que le style qui consiste à passer les valeurs directement à une autre fonction :

function renvoyerPlusieursValeurs(k) {
  k(1, 2);
}
renvoyerPlusieursValeurs((toto, truc) => ...);

Importer des noms à partir d’un module CommonJS

Vous n’utilisez pas encore les modules ES6 ? Vous utilisez toujours les modules CommonJS ? Pas de problème ! Lorsque vous importez un module CommonJS X, fréquemment, le module X exporte plus de fonctions que celles qu’on a besoin d’utiliser. Avec la décomposition, vous pouvez être explicite quant aux parties que vous souhaitez utiliser et ainsi éviter d’encombrer votre espace de noms :

const { SourceMapConsumer, SourceNode } = require("source-map");

(Si vous utilisez les modules ES6, une syntaxe similaire est disponible avec les déclarations d’import.)

Conclusion

Comme vous l’avez vu, la décomposition est utile dans de nombreux cas d’utilisation simples. À Mozilla, nous en avons une expérience étendue. En effet, la décomposition fut implémentée dans un premier temps dans JavaScript par Brendan Eich en 2006, ici aussi inspiré de Python. Elle fut livrée dans Firefox 2. Nous savons donc que la décomposition s’immisce dans l’utilisation quotidienne du langage et rend, petit à petit et partout, le code plus concis et plus propre.

Il y a cinq semaines, nous avions dit que ES6 changerait votre façon d’écrire du JavaScript. C’est précisément à ce genre de fonctionnalité que nous pensions : des améliorations simples, qui peuvent être apprises au fur et à mesure. Prises ensemble, elles finiront par affecter chaque projet sur lequel vous travaillez : une révolution composée de plein d’évolutions.

Mettre à jour la décomposition afin de se conformer à ES6 fut un véritable travail d’équipe. Remercions tout particulièrement Tooru Fujisawa (arai) et Arpad Borsos (Swatinem) pour leurs excellentes contributions.

Le support de la décomposition est en cours de développement pour Chrome et les autres navigateurs l’ajouteront sans aucun doute au fur et à mesure. Si vous souhaitez, dès maintenant, utiliser la décomposition sur le Web, vous aurez besoin de Babel ou de Traceur.


Merci encore à Nick Fitzgerald pour le billet de cette semaine.

La semaine prochaine, nous détaillerons une fonctionnalité qui n’est ni plus ni moins qu’une méthode plus courte pour écrire ce que JavaScript possède déjà et qui a toujours fait partie des briques fondamentales du langage. Est-ce que ça vous intéresse ? La promesse d’une syntaxe plus concise vous met-elle l’eau à la bouche ? Je pense que oui, mais je vous invite à voir par vous-même.

Rejoignez-nous la semaine prochaine pour explorer les fonctions fléchées ES6 en détails.