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

L’article de la semaine dernière apportait la dernière touche à ce passage en revue des nouvelles fonctionnalités apportées par ES6, débuté il y a quatre mois.

Dans ce billet, nous aborderons une douzaine d’autres fonctionnalités dont nous n’avons pas pu discuter avant. Considérez cela comme la fin du tour de la maison et la visite de certaines petites pièces situées à l’étage. Ou seraient-ce des cavernes entières cachées dessous… Si vous n’avez pas lu les autres articles, allez-y. Commencer la visite par ici n’est peut-être pas la meilleure façon de procéder. typedarrays.png « Sur votre gauche, vous pouvez observer les tableaux typés dans leur milieu »

Un dernier avertissement : parmi les fonctionnalités dont nous allons discuter, beaucoup ne sont pas encore implémentées.

OK. Commençons.

Les fonctionnalités que vous utilisez peut-être déjà

ES6 standardise certaines fonctionnalités qui étaient présentes dans d’autres standards ou qui étaient largement implémentées mais non standards.

  • Les tableaux typés, ArrayBuffer et DataView. Tout ceux-ci étaient standardisés dans le cadre de WebGL et ont depuis été utilisés dans de nombreuses autres API : Canvas, l’API Web Audio, WebRTC. Ces objets sont pratiques si vous devez manipuler de grands volumes de données binaires ou numériques.

    Par exemple, si vous souhaitez disposer d’un contexte de rendu pour Canvas et que vous êtes en forme, vous pouvez l’implémenter vous-même !

    var context = canvas.getContext("2d");
    var image = context.getImageData(0, 0, canvas.width, canvas.height);
    
    var pixels = image.data;  
    // un objet Uint8ClampedArray
    // ... Votre code !
    // ... On bidouille les bits bruts dans `pixels`
    // ... puis on les écrit sur le canevas :
    context.putImageData(image, 0, 0);
    

    Lors de la standardisation, les tableaux typés se sont vus agrémentés de méthodes comme .slice(), .map() et .filter().

  • Les promesses (objets Promise). Écrire un seul paragraphe sur les promesses, c’est un peu comme manger une seule chips. D’une part c’est très difficile et d’autre part ça n’a aucun intérêt. Que dire… Les promesses forment une brique pour programmer de façon asynchrone en JavaScript. Elles représentent des valeurs qui seront disponibles plus tard. Par exemple, lorsque vous appelez fetch(), plutôt que de bloquer le reste, la fonction renvoie immédiatement un objet Promise. L’action de fetch se passe en arrière-plan et vous pourrez vous en servir quand la réponse arrivera. Les promesses sont plus riches que les fonctions de rappel (callback) car elles peuvent être enchaînées les unes après les autres. Ce sont aussi des valeurs de premier rang avec des opérations intéressantes disponibles et la gestion des erreurs est beaucoup plus simple à mettre en œuvre. Les promesses peuvent être émulées avec des prothèses dans le navigateur. Si vous ne connaissez pas déjà les promesses, vous pouvez lire l’article, très complet, de Jake Archibald, traduit par Christophe Porteneuve sur le sujet.

  • Les fonctions dans les portées de bloc. Voici une fonctionnalité que vous ne devriez pas utiliser même si vous l’avez déjà fait… peut-être sans le vouloir.

    Depuis ES1 et jusqu’à ES5, ce code était, techniquement, illégal :

    if (temperature > 30) {
      function rafraichir() {
        return ventilo.allumer().then(boireLimonade);
      }
      rafraichir();
    }
    

    La déclaration de fonction, à l’intérieur d’un bloc if, était normalement interdite. Elle n’était autorisée qu’au plus haut niveau ou dans le bloc le plus externe de la fonction.

    Mais ça fonctionnait avec les principaux navigateurs. À peu près.

    Et pas de façon compatible. Quelques détails différaient ici et là. Toutefois ça fonctionnait dans l’ensemble et de nombreuses pages web utilisent encore un tel code.

    Dieu merci, ES6 a standardisé cet aspect. La fonction est désormais remontée en haut du bloc englobant.

    Malheureusement, Firefox et Safari n’implémentent pas encore ce nouveau standard. Pour le moment, il est donc conseillé d’utiliser une expression de fonction :

    if (temperature > 30) {
      var rafraichir = function () {
        return ventilo.allumer().then(boireLimonade);
      }
      rafraichir();
    }
    

    La seule raison pour laquelle ces fonctions n’ont pas été standardisées auparavant c’est que les contraintes de rétro-compabilité étaient incroyablement compliquées. Personne ne pensait qu’elles pourraient être résolues. ES6 a réussi ce tour de force en ajoutant une règle très étrange qui ne s’applique qu’à du code non-strict. Je ne peux pas l’expliquer ici. Croyez-moi, utilisez le mode strict.

  • Les noms de fonctions. Les principaux moteurs JS qui existent supportent depuis longtemps une propriété non-standard .name pour les fonctions qui ne sont pas anonymes. ES6 standardise cet aspect et y ajoute une amélioration pour des fonctions qui pouvaient auparavant être vues comme anonymes :

    > var inférieurÀ = function (a, b) { return a < b; };
    > inférieurÀ.name
        "inférieurÀ"
    

    Pour les autres fonctions telles que les fonctions de rappel (callbacks) qui sont passées en arguments aux méthodes .then, la spécification n’a rien pu proposer de mieux que la chaîne vide comme valeur pour fn.name.

Les trucs sympas

  • Object.assign(target, ...sources). Une nouvelle fonction de la bibliothèque standard, semblable à la méthode _.extend() d’Underscore.

  • L’opérateur de décomposition pour les appels de fonction. Rien à voir avec les cadavres dans le placard. C’est au contraire une fonctionnalité plutôt vivifiante.

    En mai, nous avons évoqué les paramètres du reste. Ils permettent à une fonction de recevoir autant d’arguments qu’on veut et remplacent agréablement l’objet arguments, un peu maladroit.

    function log(...trucs) {  
    // trucs est le paramètre du reste
    
      var rendered = trucs.map(renderStuff); 
      // C'est un vrai tableau
    
      $("#log").add($(rendered));
    }
    

    Ce que nous n’avons pas vu, c’est qu’il existe une syntaxe correspondante pour passer autant d’arguments qu’on veut à une fonction, autrement dit, une méthode plus civilisée que fn.apply() :

     // logger toutes les valeurs d'un tableau
     log(...monTableau);
    

    Bien entendu, ça fonctionne avec n’importe quel objet itérable vous pouvez donc faire de même avec un ensemble (Set) en écrivant log(...monSet).

    À la différence des paramètres du reste, il peut être judicieux que de décomposer plusieurs arguments dans un même appel :

    // l'été vient avant l'hiver
    log("Été :",...températuresÉté, "Hiver :", températuresHiver);
    

    L’opérateur de décomposition peut s’avérer utile pour aplatir un tableau de tableaux :

    > var petitsTableaux = [[], ["un"], ["deux", "trois"]];
    > var grandTableau = [].concat(...petitsTableaux);
    > grandTableau
        ["un", "deux", "trois"]
    

    mais peut-être que ce cas de figure n’intéresse que moi. Dans ce cas, la faute incombe à Haskell.

  • L’opérateur de décomposition pour construire les tableaux. En mai également, nous avions évoqué les motifs de décomposition permettant d’obtenir un nombre variable d’éléménts d’un tableau :

    > var [tête, ...queue] = [1, 2, 3, 4];
    
    > tête
           1
    > queue
           [2, 3, 4]
    

    Devinez quoi ? Il existe une syntaxe correspondante pour construire un tableau :

    > var réunion = [tête, ...queue];
    
    > réunion
          [1, 2, 3, 4]
    

    Cette syntaxe suit les mêmes règles que pour l’opérateur de décomposition appliqué aux appels de fonction : vous pouvez utiliser l’opérateur de décomposition plusieurs fois dans un même tableau et ainsi de suite.

  • Gestion de la récursion terminale. Cette fonctionnalité est trop géniale pour que je puisse l’expliquer ici.

    Pour comprendre de quoi il s’agit, je vous invite à lire Structure and Interpretation of Computer Programs. Si ça vous intéresse, continuez à lire, la récursion terminale est expliquée à la section 1.2.1 « Récursion linéaire et itération ».

    Pour respecter le standard ES6, les implémentations doivent être à récursivité terminale (au sens utilisé dans ce livre).

    Aucun des principaux moteurs JavaScript n’a encore implémenté cet aspect, d’ailleurs difficile à mettre en œuvre. Chaque chose en son temps.

Les nouveautés liées au texte

  • Mise à jour d’Unicode. Pour respecter ES5, les implémentations devaient a minima supporter tous les caractères de la version 3.0 d’Unicode. Avec ES6, les implémentations doivent au moins supporter Unicode 5.1.0. Vous pouvez désormais utiliser des caractères du syllabaires linéaire B pour nommer vos fonctions !

    Utiliser le syllabaire linéaire A est un peu risqué en revanche, en effet il n’a été ajouté à Unicode qu’à partir de la version 7.0. Aussi, ça peut être un peu compliqué de maintenir du code écrit dans un langage qui n’a jamais été déchiffré.

    (Même avec les moteurs JavaScript qui supportent l’emoji ajouté avec Unicode 6.1, vous ne pouvez pas utiliser U+1F638 comme nom de variable. Pour quelque obscure raison, le Consortium Unicode a décidé de ne pas classer ce caractère comme un identifiant. U+1F63F)

  • Séquences d’échappement Unicode étendues. ES6, comme les versions précédentes, supporte les séquences d’échappement Unicode sur quatre chiffres. Elles ressemblent à ça : \u212A. Elles sont géniales et vous pouvez les utiliser dans des chaînes de caractères. Si vous vous sentez d’humeur joueuse et que vous n’avez pas les moyens d’investir dans un outil de revue de code, vous pouvez même les utiliser dans des noms de variables. Mais lorsqu’il s’agit d’un caractère comme U+13021, le hiéroglyphe égyptien qui représente un homme debout sur sa tête, il y a un léger problème. Le nombre 13021 comporte cinq chiffres.

    Et cinq est plus grand que quatre.

    Avec ES5, il fallait écrire deux séquences d’échappement pour former une paire de demi-codets. On se serait cru aux Âges sombres : froids, tristes, barbares. ES6, tel la Renaissance italienne, apporte de lumineux changements : vous pouvez maintenant écrire \u{13021}.

  • Un meilleur support pour les caractères en dehors du BMP (plan multilingue de base). Les méthodes .toUpperCase() et .toLowerCase() fonctionnent désormais sur les chaînes écrites avec l’alphabet Deseret !

    Dans le même esprit, String.fromCodePoint(...codePoints) est une fonction, très semblable à l’ancienne String.fromCharCode(...codeUnits), qui permet de travailler avec des codets en dehors du BMP.

  • Les expressions rationnelles Unicode. Les expressions rationnelles Unicode supportent un nouveau marqueur : le marqueur u. Celui-ci permet aux expressions rationnelles de traiter les caractères en dehors du BMP comme des caractères « entiers » et non comme deux codets séparés. Par exemple, sans le marqueur u, /./ ne correspond qu’à la moitié du caractère “”. En revanche, /./u correspond à la chaîne entière.

    Utiliser le marqueur u sur une expression rationnelle permet également d’obtenir des correspondances utilisant mieux Unicode et qui gère mieux la casse et les séquences d’échappement étendues. Pour plus d’explications, rien de mieux que le billet, très détaillé, de Mathias Bynens.

  • Expressions rationnelles « adhérentes ». Une fonctionnalité, sans rapport avec Unicode, est le marqueur y, également appelé marqueur d’adhérence. Une expression rationnelle adhérente ne cherche une correspondance qu’à l’endroit indiqué par sa propriété .lastindex. S’il n’y a pas de correspondance à cet emplacement, l’expression rationnelle renvoie null directement plutôt que de chercher une correspondance plus loin sur la chaîne.

  • Une spécification officielle pour l’internationalisation. Les implémentations ES6 qui offrent des fonctionnalités d’internationalisation doivent supporter ECMA-402 : la spécification de l’API d’internationalisation d’ECMAScript 2015. Ce standard séparé spécifie l’objet Intl. Firefox, Chrome et IE11 (ainsi que les versions supérieures) le supportent déjà. Il en va de même pour Node 0.12.

Les nouveautés liées aux nombres

  • Les littéraux pour les nombres binaires et octaux. Si vous avez besoin d’écrire le nombre 8 675 309 tel un-e excentrique et que 0x845fed est trop surfait à votre goût, vous pouvez désormais l’écrire en octal (0o41057755) ou en binaire (0b100001000101111111101101).

    La fonction Number(str) reconnaît également les chaînes de caractères utilisant ce format. Par exemple, Number("0b101010") renverra 42.

    (Léger rappel : number.toString(base) et parseInt(string, base) sont les méthodes « originales » pour convertir des chaînes et des nombres en utilisant des bases arbitraires.)

  • De nouvelles fonctions et constantes pour Number. Ces changements portent sur un sujet restreint. Si cela vous intéresse, vous pouvez parcourir la spécification et voir ce que ça donne à partir de Number.EPSILON.

    La nouvelle idée peut-être la plus intéressante est l’intervalle pour représenter les entiers correctement, il va de −(253 - 1) à +(253 - 1) (au sens large). Cet intervalle numérique existe depuis le début de JS. Chaque entier compris dans cet intervalle (ainsi que ses voisins) peut être exactement représenté par un nombre JavaScript. En résumé, sur cet intervalle, ++ et -- fonctionnent sans surprise. En dehors de cet intervalle, les entiers impairs sont représentables par des nombres flottants sur 64 bits. Incrémenter ou décrémenter des nombres représentables (ceux qui sont pairs) ne peut donc pas fournir de résultat entièrement correct. Au cas où cela ait de l’importance pour votre code, vous pouvez maintenant utiliser les constantes Number.MIN_SAFE_INTEGER et Number.MAX_SAFE_INTEGER ainsi qu’une fonction de prédicat : Number.isSafeInteger(n).

  • De nouvelles fonctions pour Math. ES6 ajoute les fonctions trigonométriques hyperboliques ainsi que leurs inverses, Math.cbrt(x) pour calculer des racines cubiques, Math.hypot(x, y) pour calculer l’hypothénuse d’un triangle rectangle, Math.log2(x) et Math.log10(x) pour calculer des logarithmes sur des bases fréquemment utilisées, Math.clz32(x) pour aider à calculer des logarithmes entiers, ainsi que quelques autres fontions.

    Math.sign(x) permet d’obtenir le signe d’un nombre.

    ES6 ajoute aussi Math.imul(x, y) qui calcule la multiplication signée de deux nombres modulo 232. Plutôt étrange comme fonctionnalité, sauf si vous voulez contourner le fait que JS ne possède pas d’entiers sur 64 bits ou de système pour représenter de grands entiers. Dans ces cas, c’est plutôt pratique et ça permet d’aider les compilateurs. Emscripten utilise cette fonction pour implémenter, en JS, la multiplication des entiers représentés sur 64 bits.

    De la même façon, Math.fround(x) est utile aux compilateurs qui doivent supporter les nombres flottants sur 32 bits.

Fin

Est-ce vraiment tout ?

Pas vraiment. Je n’ai pas abordé l’objet qui est le prototype commun à tous les itérateurs natifs, ni le constructeur top-secret GeneratorFunction, ni Object.is(v1, v2), ni l’utilité de Symbol.species quand on veut créer des sous-classes d’Array et de Promise, ni la façon dont ES6 spécifie l’interaction entre plusieurs variables globales (ce qui n’avait pas été standardisé jusqu’à présent).

Je suis également certain d’avoir oublié d’autres choses.

Mais si vous avez suivi cette série depuis le début, vous devriez avoir un bon aperçu de la direction qui a été prise. Vous savez que vous pouvez utiliser les fonctionnalités ES6 dès aujourd’hui et qu’en faisant ça, vous utiliserez un meilleur langage.

Il y a quelques jours, Josh Mock m’a fait remarquer qu’il avait utilisé huit fonctionnalités ES6 différentes sur environ 50 lignes de code, sans même y penser : les modules, les classes, les arguments par défaut, Set, Map, les gabarits de chaînes de caractères, les fonctions fléchées et let (il avait oublier de mentionner la boucle for-of).

C’est également ce que j’ai ressenti. Ces nouvelles fonctionnalités s’imbriquent très bien. Elles finissent par affecter chaque ligne de JS que vous écrivez.

Pendant ce temps, tous les moteurs JS se dépêchent d’implémenter et d’optimiser les fonctionnalités que nous avons vues au cours de ces derniers mois.

Une fois que ce sera fini, le langage sera terminé. Rien n’aura besoin d’être changé et je devrai trouver quelque chose d’autre sur quoi travailler.

Je rigole, les propositions pour ES7 sont déjà sur la rampe de lancement. En voici quelques-unes :

  • L’opérateur d’exponentiation. 2 ** 8 renverra 256. Celui-ci est implémenté dans Firefox Nightly.

  • Array.prototype.includes(valeur). Renvoie true si le tableau contient la valeur donnée. Cette méthode est implémentée dans Firefox Nightly et peut être remplacée par une prothèse (polyfill).

  • SIMD. Cette API expose les instructions SIMD 128 bits fournies par les processeurs récents. Ces instructions permettent d’effectuer, en une fois, des opérations arithmétiques sur des tableaux de 2, 4 ou 8 éléments adjacents. Elles peuvent améliorer les performances de certains algorithmes de façon drastique : traitement audio et vidéo, cryptographie, jeux, etc. Ces fonctions se situent à un très bas niveau et sont très puissantes. Elles sont implémentées dans Firefox Nightly et peuvent être remplacées par une prothèse.

  • Les fonctions asynchrones. Nous avons entraperçu cette fonctionnalité dans le billet sur les générateurs. Les fonctions asynchrones ressemblent aux générateurs mais sont spécialisées pour la programmation asynchrone. Quand vous appelez un générateur, il renvoie un itérateur. Quand vous appelez une fonction asynchrone, elle renvoie une promesse. Les générateurs utilisent le mot-clé yield pour faire une pause et produire une valeur, les fonctions asynchrones, quant à elles, utilisent le mot-clé await pour faire une pause et attendre une promesse.

    C’est plutôt difficile de les décrire en quelques lignes mais il y a fort à parier que les fonctions asynchrones seront la fonctionnalité phare de ES7.

  • Les objets typés. La suite logique des tableaux typés. Les tableaux typés ont des éléments qui peuvent être typés. Un objet typé est simplement un objet dont les propriétés sont typées.

    // On crée une nouvelle structure typée. Chaque a deux propriétés
    // appelées x et y.
    var Point = new TypedObject.StructType({
      x: TypedObject.int32,
      y: TypedObject.int32
    });
    
    // On crée une instance de ce type.
    var p = new Point({x: 800, y: 600});
    console.log(p.x); // 800
    

    Les performances sont la principale (voire la seule) raison d’utiliser cela. Comme les tableaux typés, les objets typés apportent les bénéfices d’un typage fort (utilisation de mémoire, vitesse) mais au choix et à la granularité des objets, contrairement aux langages où tout est typé de façon statique.

    Il sont également pertinents si on utilise JS comme langage cible pour compiler.

    Les objets typés sont implémentés dans Firefox Nightly.

  • Décorateurs de classes et de propriétés. Les décorateurs sont des étiquettes que vous pouvez ajouter à une propriété, une classe ou une méthode. Voici un exemple pour les illustrer :

    import debug from "jsdebug";
    class Personne {
        @debug.logSiAppelé
        teteRonde(assert) {
        return this.tete instanceof Spheroide;
        }
        ...
    }
    

    c’est @debug.logSiAppelé qui est le décorateur ici. Je vous laisse imaginer ce qu’il fait à la méthode.

    La proposition pour le standard explique comment cela fonctionne, en détails et avec de nombreux exemples.

Encore une nouveauté intéressante que je dois mentionner. Il ne s’agit pas d’une fonctionnalité du langage.

TC39, le comité pour le standard ECMAScript va augmenter le rythme des versions et adopter un processus plus transparent. Six ans se sont écoulés entre ES5 et ES6. Le comité a l’intention de publier ES7 12 mois après ES6. Les éditions suivantes du standard seront publiées à ce rythme annuel. Certaines des fonctionnalités listées ci-dessus seront prêtes à temps. Elles « monteront dans le train » et feront partie d’ES7. Celles qui ne sont pas finalisées durant cette période prendront le prochain.

J’ai adoré partager cette quantité stupéfiante de nouveautés apportées par ES6. Bonne nouvelle, une telle vague ne devrait probablement pas se reproduire. Merci d’avoir suivi ES6 en détails. J’espère que ces articles vous ont plu. À la prochaine !


Fin de cette série sur ECMAScript 6. Ce fut un plaisir que de travailler sur ces traductions au fur et à mesure que Jason Orendorff avançait. De sincères et chaleureux remerciements s’imposent pour ces 17 articles, ces milliers de mots et ces dizaines d’exemples :

  • Merci à Marine, Banban, Ilphrin, Jérémie, Mentalo, Thegennok, Lucas, Benjamin, Benoît et Amarok qui ont participé aux traductions de ces articles, que ce soit pour quelques lignes, de nombreux paragraphes, voire des articles. Sans ce magnifique travail d’équipe, cette série n’aurait pas pu avancer à ce rythme ! Bravo tout le monde !
  • Merci à goofy qui a été très patient tout au long de ces quatre mois pour relire chacun des articles tout en gardant un œil sur SUMO. Puisse la force du Bescherelle et les règles typographiques t’apporter joie et bonheur :)
  • Merci à Pascal Chevrel pour avoir répondu à mes questions sur Dotclear et pour faire ce travail de l’ombre qu’on sous-estime trop souvent : la maintenance, en l’occurrence celle de ce blog.
  • Merci à celles et ceux qui ont partagé ces articles.
  • Merci à Framasoft pour ses supers framapads !
  • Merci aux auteurs de la série : Eric Faust, Gastón I. Silva, Nick Fitzgerald, Benjamin Peterson…

Enfin et surtout, merci à Jason Orendorff, de m’avoir appris beaucoup de choses sur JavaScript, d’avoir trouvé des jeux de mots assez tordus à traduire, d’utiliser MDN et d’avoir accepté que ce projet puisse continuer à vivre sous une autre forme qui devrait arriver prochainement…

Qui sait, peut-être à bientôt sur ce blog !