Bidouilleux d'Web - Mot-clé - HacksLe blog technique de la communauté Mozilla francophone2022-02-04T14:07:43+01:00Communauté Mozilla francophoneurn:md5:935a9b6df47b29d6dc8c2ca47296b179DotclearDétails techniques sur la panne des modules complémentaires de Firefoxurn:md5:792560e8e1288351b86614afaf2e346b2019-05-10T18:52:00+02:002019-05-11T16:08:31+02:00sphinxGénéralCorrectifFirefoxHacksmodules complémentairesMozilla<p><em>Cet article est une traduction de</em> <a href="https://hacks.mozilla.org/2019/05/technical-details-on-the-recent-firefox-add-on-outage/">Technical Details on the Recent Firefox Add-on Outage</a>, <em>écrit par Eric Rescorla, directeur technique de l’équipe de Firefox. Pour un aperçu moins technique, vous pouvez lire <a href="https://blog.mozfr.org/post/2019/05/Ce-que-nous-faisons-quand-les-choses-tournent-mal" title="Ce que nous faisons quand les choses tournent mal (10 mai 2019) Communauté Mozilla francophone">cet article</a>. Merci à Vincent pour la relecture !</em></p>
<hr />
<p>Il y a quelques jours, Firefox a rencontré un problème empêchant la plupart des modules complémentaires de fonctionner. Nous avons commis une erreur en laissant expirer un des certificats utilisés pour la signature des modules. Cela a eu pour effet de désactiver une majeure partie des modules. Maintenant que ce problème a été corrigé pour la plupart des utilisateurs et que la plupart des modules sont revenus à la normale, nous souhaitions revenir sur les détails de ce problème, les raisons de son origine et la façon dont nous l’avons résolu.</p>
<h2>Un peu de contexte : les modules complémentaires et leur signature</h2>
<p>Bien que de nombreuses personnes utilisent Firefox tel quel, il est également possible d’étendre ses fonctionnalités grâce à des « modules complémentaires » (aussi appelées <em>add-ons</em> ou extensions). Les modules permettent ainsi aux utilisateurs d’ajouter des fonctionnalités tierces qui complètent les fonctionnalités par défaut. Il existe aujourd’hui plus de 15 000 modules pour Firefox qui permettent par exemple de <a href="https://addons.mozilla.org/firefox/addon/ublock-origin/" title="Extension uBlock Origin">bloquer des publicités</a> ou de <a href="https://addons.mozilla.org/firefox/addon/tree-style-tab/" title="Extension Tree Style Tab">gérer des centaines d’onglets</a>.</p>
<p>Pour qu’un module puisse être installé dans Firefox, il faut qu’il soit <a href="https://blog.mozilla.org/addons/2015/02/10/extension-signing-safer-experience/">signé numériquement</a>. Cette condition fait partie des mesures visant à protéger les utilisateurs de modules malveillants et implique au moins une revue de certains standards dans le module par l’équipe Mozilla. Cette mesure a été introduite en 2015 après avoir rencontré de <a href="https://blog.mozilla.org/addons/2015/04/15/the-case-for-extension-signing/">graves problèmes</a> avec des modules malveillants.</p>
<p>Pour signer numériquement un module, Firefox est configuré avec un « certificat racine » préinstallé. Cette racine est stockée hors ligne dans une <a href="https://fr.wikipedia.org/wiki/Hardware_Security_Module">boîte noire transactionnelle</a> (ou <em>hardware security module</em> en anglais, abrégé en HSM). Toutes les quelques années, ce certificat racine est utilisé afin de signer un nouveau « certificat intermédiaire » qui est conservé en ligne et utilisé dans le processus de signature. Lorsqu’un module est envoyé pour être signé, nous générons un nouveau certificat temporaire : le certificat d’entité final et nous le signons avec le certificat intermédiaire. C’est ce certificat d’entité final qui est utilisé afin de signer le module.</p>
<p>Voici un schéma qui illustre ce fonctionnement :</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/armagaddon2/Add-on-blog-post-visual-May-7-2019_fr.png" title="Illustration des liens de signatures entre les certificats et les modules"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/armagaddon2/.Add-on-blog-post-visual-May-7-2019_fr_m.png" alt="Illustration des liens de signatures entre les certificats et les modules" style="margin: 0 auto; display: block;"/></a></p>
<p>On pourra voir que chaque certificat possède un sujet (l’entité à laquelle le certificat appartient) et un émetteur (celui qui l’a signé). Pour le certificat racine, l’émetteur et le sujet sont les mêmes. Pour les autres certificats en revanche, c’est l’émetteur du certificat est le sujet du certificat ayant servi à la signature.</p>
<p>On pourra noter un point important : chaque module est signé par un certificat d’entité final qui lui est propre mais la quasi-intégralité des modules partage le même certificat intermédiaire <a href="https://tech.mozfr.org/post/2019/05/10/Details-techniques-sur-la-panne-des-modules-complementaires-de-Firefox#exp1"><sup>[1]</sup></a>. C’est ce certificat qui a posé problème. En effet, chaque certificat est valide pendant une période de temps donnée. Avant ou après cette période, le certificat ne sera plus accepté et les modules signés avec ce certificat ne pourront plus être chargés dans Firefox. Malheureusement, le certificat intermédiaire que nous utilisions a expiré le 4 mai après 01:00 <abbr lang="en" title="Universal Time Coordinated">UTC</abbr>. Après cet instant, chaque module signé avec ce certificat intermédiaire est devenu invérifiable et ne pouvait plus être chargé dans Firefox.</p>
<p>Toutefois, bien que les modules aient expiré autour de minuit, l’impact de cet arrêt ne s’est pas fait ressentir immédiatement. En fait, les modules sont vérifiés toutes les 24 heures et l’heure de vérification est différente pour chaque utilisateur. Aussi, certaines personnes ont eu le problème immédiatement tandis que d’autres ne l’ont rencontré que bien plus tard. À Mozilla, nous avons réalisé ce problème le 3 mai à 18 h 00 (heure du Pacifique) et avons immédiatement regroupé une équipe afin de le résoudre.</p>
<h2>Circonscrire les dégâts</h2>
<p>Après avoir réalisé de quoi il en retournait, nous avons pris plusieurs mesures pour éviter d’empirer les choses.
Pour commencer, nous avons désactivé la signature pour les nouveaux modules. Nous avons pris cette décision, car nous savions que le certificat utilisé pour la signature avait expiré. Avec le recul, nous aurions pu laisser poursuivre cette signature mais cela aurait interféré avec une solution d’atténuation consistant à inscrire une date en dur (cf. ci-après, au final, nous n’avons pas utilisé cette solution).</p>
<p>Ensuite, nous avons immédiatement envoyé un correctif afin de supprimer le mécanisme consistant à revérifier les modules. L’idée visait à éviter de casser le fonctionnement des modules pour les utilisateurs pour lesquels la validation quotidienne n’avait pas encore eu lieu. Nous avons appliqué ce correctif avant d’en avoir d’autres et nous l’avons retiré maintenant que des correctifs plus pérennes sont disponibles.</p>
<p>À l’heure actuelle, la signature des nouveaux modules fonctionne à nouveau.</p>
<h2>Travailler en parallèle</h2>
<p>En théorie, résoudre un tel problème semble plutôt simple : on crée un nouveau certificat valide puis on publie à nouveau les modules avec ce certificat.</p>
<p>Malheureusement, nous avons vite constaté que cela ne fonctionnerait pas pour plusieurs raisons :</p>
<ol>
<li>Il existe une multitude de modules (plus de 15 000) et le service utilisé pour la signature n’est pas optimisé pour signer en masse, resigner chaque module prendrait plus de temps que ce que nous voulions ;</li>
<li>Une fois les modules signés, les utilisateurs auraient dû récupérer les nouvelles versions de leurs modules. Certains de ces modules sont hébergés sur les serveurs de Mozilla et Firefox aurait mis à jour ces modules en 24 heures. Toutefois, les utilisateurs auraient dû mettre à jour manuellement les modules installés depuis d’autres sources : cela se serait avéré plus que gênant.</li>
</ol>
<p>À la place, nous nous sommes concentrés sur le développement d’un correctif que nous pourrions fournir à l’ensemble de nos utilisateurs et qu’il y ait le minimum d’intervention manuelle.</p>
<p>Après avoir étudié différentes approches, nous avons rapidement convergé vers deux stratégies principales que nous avons menées en parallèle :</p>
<ol>
<li>Corriger Firefox afin de modifier la date utilisée pour valider le certificat. Cela permettrait aux modules de fonctionner à nouveau par enchantement, mais il fallait produire et distribuer une nouvelle version de Firefox ;</li>
<li>Générer un certificat de remplacement toujours valide et, d’une certaine façon, convaincre Firefox de l’accepter plutôt que le certificat existant expiré.</li>
</ol>
<p>Nous n’étions pas certains que l’une de ces deux solutions fonctionnerait et nous avons décidé de les mener en parallèle et de déployer la première qui semblerait fonctionner. En fin de journée, nous avons fini par déployer ce deuxième correctif, un nouveau certificat de remplacement, que nous décrirons ensuite en détail.</p>
<h2>Un certificat de remplacement</h2>
<p>Comme expliqué ci-avant, cette solution se décomposait en deux étapes :</p>
<ol>
<li>Générer un nouveau certificat qui soit valide ;</li>
<li>L’installer à distance dans Firefox.</li>
</ol>
<p>Pour comprendre comment cela fonctionne, il nous faut plonger plus en détails dans la façon dont Firefox valide les modules. Le module est constitué d’un ensemble de fichier incluant la chaîne de certificat utilisée pour le signer. Ainsi, le module peut être vérifié indépendamment tant qu’on connaît le certificat racine (configuré dans Firefox lors de la compilation). Toutefois, comme nous l’avons dit, le certificat intermédiaire ayant expiré, le module ne pouvait être vérifié.</p>
<p>En réalité, lorsque Firefox tente de valider un module, il ne se limite pas à utiliser les certificats contenus dans le module. Il essaie en fait de construire une chaîne de certificats valide en commençant par le certificat d’entité final et en remontant jusqu’à la racine. L’algorithme même est compliqué, mais on peut le résumer ainsi : on commence par le certificat d’entité final et on trouve ensuite un certificat dont le sujet est égal à l’émetteur du certificat final (ici il s’agit normalement du certificat intermédiaire). Dans un scénario simple, le navigateur remonte au certificat intermédiaire, mais il pourrait tout à fait s’agir d’un certificat que le navigateur connaît. Si nous pouvons ajouter à distance un nouveau certificat, valide, Firefox pourrait vérifier ce certificat plutôt que celui qui est expiré. Le schéma qui suit illustre la situation avant et après l’installation du nouveau certificat :</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/armagaddon2/Add-on-blog-post-visual-2-May-7-2019_fr.png" title="Schéma avant/après illustrant l'ajout d'un maillon avec un certificat intermédiaire valide"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/armagaddon2/.Add-on-blog-post-visual-2-May-7-2019_fr_m.png" alt="Schéma avant/après illustrant l'ajout d'un maillon avec un certificat intermédiaire valide" style="margin: 0 auto; display: block;" /></a></p>
<p>Une fois le nouveau certificat installé, Firefox a désormais le choix entre deux certificats pour valider la chaîne de certificats : le certificat expiré (invalide) et le nouveau certificat (valide). Un élément important permet que cela fonctionne : le nouveau certificat possède le même sujet et la même clé publique que l’ancien certificat et sa signature sur le certificat d’entité final est donc valide. Heureusement, Firefox est suffisamment intelligent pour essayer chacune des pistes jusqu’à trouver un chemin qui fonctionne afin que le module soit à nouveau valide.</p>
<p>On notera ici que c’est la même logique qui est à l’œuvre pour valider les certificats <abbr lang="en" title="Transport Layer Security">TLS</abbr> : il s’agit donc d’un code bien connu que nous avons pu utiliser.<a href="https://tech.mozfr.org/post/2019/05/10/Details-techniques-sur-la-panne-des-modules-complementaires-de-Firefox#exp2"><sup>[2]</sup></a></p>
<p>Le grand avantage de cette méthode est qu’elle ne nécessite pas de modifier les modules. Tant que le nouveau certificat peut être fourni à Firefox, les modules (y compris ceux portant le certificat expiré) pourront être vérifiés automatiquement. Là où ça devient donc compliqué, c’est qu’il faut envoyer le nouveau certificat dans Firefox, automatiquement et à distance puis faire le nécessaire afin que Firefox revérifie les modules qui auraient pu être désactivés.</p>
<h2>Normandy et le système d’études</h2>
<p>Avec une certaine ironie, le véhicule utilisé pour la solution à ce problème a été un « module système » (ou <em>system add-on</em> abrégé en SAO en anglais) qui est un type de module spécial. Afin de pouvoir mener des études de recherche, nous avons développé un système intitulé Normandy qui nous permet de distribuer des modules système aux utilisateurs de Firefox. Ces modules système sont exécutés automatiquement dans le navigateur de l’utilisateur. Bien qu’ils soient généralement utilisés pour lancer des tests, ils possèdent également un accès privilégié aux API internes de Firefox. Ils peuvent notamment ajouter de nouveaux certificats à la base de données utilisée par Firefox pour vérifier les modules.<a href="https://tech.mozfr.org/post/2019/05/10/Details-techniques-sur-la-panne-des-modules-complementaires-de-Firefox#exp3"><sup>[3]</sup></a></p>
<p>Le correctif consiste donc à construire un module système qui réalise deux choses :</p>
<ol>
<li>Installer le nouveau certificat ;</li>
<li>Forcer le navigateur à revérifier chaque module afin que les modules désactivés puissent être activés à nouveau.</li>
</ol>
<p>Mais… si les modules ne fonctionnent plus, comment exécuter ce module système ? Eh bien en le signant avec le nouveau certificat !</p>
<h2>Récapitulons… pourquoi tout ce temps ?</h2>
<p>Nous avons donc un plan : émettre un nouveau certificat afin de remplacer l’ancien, construire un module système à installer sur Firefox et le déployer via Normandy. Après avoir commencé à travailler sur le sujet le 3 mai à 18 h 00 (heure du Pacifique), nous déployions le correctif via Normandy à 2 h 44 le lendemain matin (soit moins de 9 heures), 6 à 12 heures se sont ensuite écoulées avant que la plupart de nos utilisateurs en bénéficient.</p>
<p>Il s’agit d’une durée assez courte, mais nous avons vu plusieurs questions sur Twitter nous demandant pourquoi nous n’avions pas pu aller plus vite.</p>
<p>Plusieurs étapes ont été chronophages.</p>
<p>Premièrement, il a fallu un certain temps pour émettre le nouveau certificat intermédiaire. Comme indiqué ci-avant, le certificat racine est situé dans une boîte noire transactionnelle stockée hors ligne. Il s’agit d’une règle de sécurité importante : le certificat racine est rarement utilisé et on veut qu’il soit sécurisé. En revanche, ce n’est pas idéal lorsqu’on souhaite émettre un nouveau certificat en urgence. En tout cas, un de nos ingénieurs a dû conduire à l’endroit où la boîte noire était stockée. Ensuite, nous avons eu quelques faux départs où nous n’avons pas exactement émis le bon certificat, chaque tentative demandant une à deux heures de tests avant de pouvoir être certains.</p>
<p>Deuxièmement, le développement d’un module système prend du temps. Conceptuellement, c’est quelque chose de très simple mais même les programmes simples nécessitent une certaine attention et nous voulions à tout prix éviter d’empirer les choses. Puis, avant de livrer le module système, il a fallu le tester. Ces tests ont pris du temps, notamment parce qu’il fallait signer ce module et que le système de signature était désactivé : il nous a donc fallu trouver des méthodes de contournement.</p>
<p>Enfin, une fois le module système prêt à être livré, il y a toujours un temps incompressible au déploiement. Les clients Firefox contactent Normandy toutes les 6 heures et, bien entendu, de nombreux clients sont déconnectés : le correctif prendra donc un certain temps à se propager à l’ensemble de la population utilisant Firefox. À l’heure actuelle (9 mai 2019), nous pensons que la plupart des personnes ont reçu cette mise à jour et/ou la mise à jour mineure effectuée ensuite.</p>
<h2>Les dernières étapes</h2>
<p>Bien que le module système déployé avec Normandy et les études devrait corriger le problème pour la plupart des utilisateurs, il n’est pas parvenu jusqu’à tout le monde. Certains utilisateurs restent notamment affectés et pour ceux-là, une autre approche sera nécessaire :</p>
<ul>
<li>les utilisateurs ayant désactivé la télémétrie ou les études ;</li>
<li>les utilisateurs de Firefox pour Android (Fennec) qui ne possède pas de système d’étude ;</li>
<li>les utilisateurs des versions dérivant de Firefox ESR qui n’activent pas la télémétrie ;</li>
<li>les utilisateurs situés derrière des <em>proxies</em> HTTPS en « homme du milieu » : notre système d’installation de module oblige la vérification de certaines clés épinglées (<em>key pinning</em>) et les <em>proxies</em> interfèrent avec celles-ci ;</li>
<li>les utilisateurs de très anciennes versions de Firefox que ne peut pas atteindre le système d’études.</li>
</ul>
<p>En ce qui concerne ce dernier groupe, nous ne pouvons pas vraiment agir : ces utilisateurs devraient mettre à jour leur version de Firefox, car les anciennes versions sont sujettes à de graves vulnérabilités de sécurité non corrigées. Nous sommes conscients des personnes ayant conservé une ancienne version de Firefox afin de pouvoir exécuter d’anciens modules, mais la plupart fonctionne maintenant avec les nouvelles versions de Firefox.</p>
<p>Pour les autres groupes, nous avons développé un correctif pour Firefox qui installera le nouveau certificat lorsque les utilisateurs mettront à jour leur navigateur. Ce correctif a été émis avec une version mineure et la plupart pourront en bénéficier via le mécanisme de mise à jour classique (en réalité, ce devrait déjà être le cas). Si vous utilisez une version dérivée, vous devrez attendre une nouvelle mise à jour de la part du responsable de cette version dérivée.</p>
<p>Nous sommes conscients qu’aucune de ces solutions n’est parfaite. Des données relatives aux modules et aux utilisateurs ont notamment pu être perdues (par exemple avec le module <em><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1549204" title="bug 1549204">Firefox Multi-Account Containers</a></em>).</p>
<p>Nous n’avons pas été capables de développer un correctif qui aurait pu empêcher cet effet de bord et nous pensons que l’approche utilisée a été la meilleure pour nos utilisateurs à court terme. Sur le long terme, nous étudierons d’autres approches architecturales afin de gérer ce type de problème.</p>
<h2>Ce que nous avons appris</h2>
<p>Pour commencer, je souhaiterais féliciter l’équipe qui a accompli un travail extraordinaire : ils ont construit et déployé un correctif en moins de 12 heures après la détection du problème. Pour avoir assisté à ce travail, je peux dire que ces personnes ont travaillé d’arrache-pied, dans un contexte difficile et que peu de secondes ont été gaspillées.</p>
<p>Ceci étant dit, il est évident que cette situation est loin d’être idéale et n’aurait pas dû se produire pour commencer. Nous devons ajuster nos procédures pour réduire la probabilité de tels incidents et aussi pour les corriger plus facilement. Nous réaliserons une analyse rétrospective la semaine prochaine et publierons les modifications que nous souhaitons apporter. En attendant, voici mes premières réflexions sur ce que nous devons faire. Pour commencer, nous devons avoir une meilleure méthode pour vérifier le statut de chaque élément temporel contenu dans Firefox, sans quoi, cela risque d’exploser à tout moment : nous ne devons pas être pris par surprise. Nous travaillons encore là-dessus, mais il nous faut au moins inventorier tous les éléments de cette nature.</p>
<p>Ensuite, il nous faut un mécanisme qui permette de rapidement diffuser des mises à jour à nos utilisateurs lorsque tout le reste est cassé (surtout quand tout le reste est cassé). Nous avons pu tirer parti du système des études, mais il s’agissait d’un outil imparfait que nous avons mobilisé et qui a entraîné certains effets de bord indésirables.</p>
<p>Nous savons notamment que de nombreux utilisateurs activent les mises à jour automatiques mais préfèrent ne pas participer aux études : ce choix est respectable (pour être honnête, je l’avais également désactivé). En même temps, nous devons être capables de diffuser des mises à jour à nos utilisateurs quel que soit le véhicule technique. Les utilisateurs devraient pouvoir activer les mises à jour (y compris les correctifs d’urgence) et pouvoir désactiver tout le reste.</p>
<p>De plus, le canal de mise à jour doit être plus réactif. Nous avons eu lundi des utilisateurs qui n’avaient pas encore récupéré le correctif ou la version mineure : c’est loin d’être idéal. Certains travaux sont et étaient déjà en cours à cet égard mais cet incident montre combien cet effort est important.</p>
<p>Enfin, nous étudierons plus généralement l’architecture relative à la sécurité des modules pour nous assurer qu’un niveau de sécurité suffisant est respecté tout en minimisant les risques de panne.</p>
<p>Nous donnerons suite la semaine prochaine avec les résultats d’une analyse rétrospective plus poussée mais en attendant, je serai ravi de répondre à vos questions (en anglais) à ekr-blog[AD]mozilla[POINT]com.</p>
<p>[1]<a name="exp1"></a> Quelques modules très anciens étaient signés avec un certificat intermédiaire différent.</p>
<p>[2]<a name="exp2"></a> Les personnes familières avec WebPKI reconnaîtront la méthode également utilisée pour la certification croisée.</p>
<p>[3]<a name="exp3"></a> Note technique : nous n’ajoutons pas un certificat avec un privilège quelconque. L’autorité de ce certificat provient de la racine avec laquelle il a été signé. Nous ajoutons uniquement un nouveau certificat intermédiaire à l’ensemble de certificats qui peuvent être utilisés dans Firefox. Autrement dit, nous n’ajoutons pas un nouveau certificat, privilégié d’une quelconque façon, dans Firefox.</p>
Une plongée illustrée dans les modules ECMAScripturn:md5:bd76f81d14a21c270a6f686781001bb52018-04-09T07:45:00+02:002018-04-09T07:04:35+02:00sphinxJavaScriptCommonJSECMAScriptHacksimportJavaScriptModuleNode.JSrequire<h2><em>Cet article est une traduction de <a href="https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/">ES modules: A cartoon deep-dive</a>, écrit par <a href="http://twitter.com/linclark">Lin Clark</a>. Merci à goofy pour la relecture !</em></h2>
<p>Les modules ECMAScript (ou modules ES) sont un système de modules, standard et officiel, pour JavaScript. Ce système est le fruit d’un travail de standardisation qui a duré 10 ans.</p>
<p>Nous pourrons bientôt profiter de tout cela avec l’arrivée des modules dans Firefox 60 en mai (<a href="https://www.mozilla.org/firefox/developer/">cette version est actuellement en bêta</a>). Les navigateurs principaux prennent tous en charge cette fonctionnalité et le groupe de travail pour les modules Node contribue à l’ajout des modules ES dans <a href="https://nodejs.org">Node.js</a>. <a href="https://www.youtube.com/watch?v=qR_b5gajwug">L’intégration des modules ES dans WebAssembly</a> a également démarré.</p>
<p>De nombreux développeurs JavaScript savent que les modules ES ont été à l’origine de différentes controverses mais peu comprennent réellement le fonctionnement des modules ES.</p>
<p>Voyons ici les problèmes résolus par les modules ES et leurs différences avec les autres systèmes de modules.</p>
<h2>Les problèmes résolus par les modules</h2>
<p>Quand on y pense, coder en JavaScript revient principalement à gérer des variables. On leur affecte des valeurs, on leur ajoute des nombres, on combine deux variables pour construire une nouvelle variable.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/01_variables.png" title="Code illustrant la manipulation de variables"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.01_variables_m.png" style="margin: 0 auto; display: block;" title="Code illustrant la manipulation de variables"/></a></p>
<p>Étant donné la proportion de code consacrée à la modification des variables, l’organisation de ces variables aura un impact non-négligeable sur la qualité du code… mais aussi sur la facilité à le maintenir.</p>
<p>S’occuper d’un nombre de variables limité permet de simplifier les choses. En JavaScript, on peut s’aider des portées (<em>scope</em>) pour limiter le périmètre des variables qu’on doit gérer. En JavaScript, les portées formées par les fonctions empêchent ces dernières d’accéder à des variables qui seraient définies dans d’autres fonctions.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/02_module_scope_01.png" title="Deux portées de fonction qui essaient d'accéder au contenu de l'autre"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.02_module_scope_01_m.png" style="margin: 0 auto; display: block;" title="Deux portées de fonction qui essaient d'accéder au contenu de l'autre"/></a></p>
<p>C’est une bonne chose. Cela signifie que lorsqu’on travaille sur une fonction, on peut ne penser qu’à cette fonction. Il n’est pas nécessaire de s’encombrer l’esprit avec l’impact potentiel des autres fonctions sur les variables de la fonction courante.</p>
<p>Toutefois, cela a un prix : il est plus difficile de partager des variables entre différentes fonctions.</p>
<p>Comment partager une variable en dehors de sa portée ? Pour répondre au problème, on utilise généralement une portée plus grande… comme la portée globale.</p>
<p>Peut-être vous souvenez vous de l’époque où jQuery était utilisé. Avant de pouvoir utiliser un <em>plugin</em> jQuery, il fallait s’assurer que jQuery faisait bien partie de la portée globale.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/02_module_scope_02.png" title="Deux portées de fonction au sein d'une portée globale qui contient jQuery"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.02_module_scope_02_m.png" style="margin: 0 auto; display: block;" title="Deux portées de fonction au sein d'une portée globale qui contient jQuery"/></a></p>
<p>On a répondu à ce problème de partage des variables mais cela n’est pas sans conséquence.</p>
<p>Tout d’abord, il faut que les balises de script soient dans le bon ordre. Ensuite, il faut s’assurer que personne ne vienne chambouler cet ordre.</p>
<p>Si cet ordre vient à être chamboulé, l’application pourra causer une belle erreur au plein milieu de l’exécution. Lorsque la fonction qui va chercher jQuery où elle s’attend à l’avoir (c’est-à-dire dans la portée globale) et ne le trouve pas, elle déclenchera une erreur et arrêtera son exécution.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/02_module_scope_03.png" title="La portée de la première fonction a été supprimée et la portée de la seconde fonction ne peut pas trouver jQuery dans la portée globale"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.02_module_scope_03_m.png" style="margin: 0 auto; display: block;" title="La portée de la première fonction a été supprimée et la portée de la seconde fonction ne peut pas trouver jQuery dans la portée globale"/></a></p>
<p>La maintenance du code devient plus épineuse. Retirer du vieux code ou des balises <code><script></code> s’apparente à un pari risqué. Personne ne peut savoir ce qui va être cassé. Les dépendances entre les différentes parties du code sont implicites. N’importe quelle fonction peut manipuler n’importe quoi appartenant à la portée globale et on ne sait plus de quels scripts dépendent ces fonctions.</p>
<p>Ce n’est pas tout, les variables qui sont dans la portée globale peuvent très bien être modifiées par n’importe quel code situé au sein de cette portée. Du code malveillant pourrait modifier cette variable globale à dessein afin de détourner l’exécution. Du code <em>a priori</em> innocent pourrait également, accidentellement, modifier la variable.</p>
<h2>Qu’apportent les modules ?</h2>
<p>Les modules permettent de mieux organiser les variables et les fonctions. Avec les modules, on peut regrouper les variables et les fonctions de façon pertinente.</p>
<p>Ainsi, ces fonctions et variables font partie de la portée d’un module et cette portée peut être utilisée afin de partager des variables entre les fonctions du module.</p>
<p>Cependant, à la différence des portées créées par les fonctions, les portées des modules permettent de rendre leurs variables disponibles pour d’autres modules. On peut indiquer, explicitement, les variables, les classes ou les fonctions qui seront disponibles pour être utilisées ailleurs.</p>
<p>Quelque chose qui est rendu disponible pour un autre module est appelé un <strong>export</strong>. Lorsqu’on dispose d’un export, les autres modules peuvent, de façon explicite, indiquer qu’ils dépendent de cette variable, classe ou fonction.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/02_module_scope_04.png" title="Deux portées de modules avec la deuxième utilisant un export du premier"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.02_module_scope_04_m.png" style="margin: 0 auto; display: block;" title="Deux portées de modules avec la deuxième utilisant un export du premier"/></a></p>
<p>Cette relation étant explicite, on est cette fois en mesure de déterminer ce qui va casser si on retire un des modules.</p>
<p>Une fois capable d’exporter et d’importer des variables entre les modules, on peut diviser le code en petits fragments qui peuvent fonctionner de façon indépendante. On peut alors combiner et recombiner ces fragments, tels des briques de Lego, afin de créer différentes applications à partir d’un même ensemble de modules.</p>
<p>L’organisation en module apportant de nombreux bénéfices, plusieurs tentatives ont été lancées afin d’incorporer ces modules dans JavaScript. Il existe aujourd’hui deux systèmes de module activement utilisés :
* CommonJS (CJS), utilisé par Node.js jusqu’à présent.
* ESM (<em>ECMAScript modules</em>), un système plus récent qui a été ajouté à la spécification JavaScript.</p>
<p>Les navigateurs prennent déjà en charge les modules ES et Node travaille à leur implémentation.</p>
<p>Voyons ensuite le détail du fonctionnement de ce nouveau système de modules.</p>
<h2>Le fonctionnement des modules ES</h2>
<p>Lorsqu’on développe en utilisant des modules, on construit un graphe de dépendances. Les liens entre ces dépendances proviennent des instructions <code>import</code> utilisées.</p>
<p>Ces instructions <code>import</code> indiquent, au moteur du navigateur ou à celui de Node, les fragments de code à charger. Il suffit de donner le nom d’un fichier comme point d’entrée pour le graphe puis le moteur suivra chacune des instructions <code>import</code> afin de récupérer l’ensemble du code nécessaire.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/04_import_graph.png" title="Un module avec deux dépendances. Le module du haut est le point d'entrée et les deux autres sont liés par la présence d'instructions import"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.04_import_graph_m.png" style="margin: 0 auto; display: block;" title="Un module avec deux dépendances. Le module du haut est le point d'entrée et les deux autres sont liés par la présence d'instructions import"/></a></p>
<p>Mais le navigateur ne peut pas utiliser les fichiers directement. Il faut qu’il analyse ces fichiers afin de les transformer en structures de données appelées <em>Module Records</em>. Ainsi, il peut savoir ce qui se passe dans chaque fichier.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/05_module_record.png" title="Un module record avec différents champs et notamment RequestedModules et ImportEntries"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.05_module_record_m.png" style="margin: 0 auto; display: block;" title="Un module record avec différents champs et notamment RequestedModules et ImportEntries"/></a></p>
<p>Après cette analyse, le <em>module record</em> doit être converti en une <em>instance de module</em>. Une instance représente deux choses : le code et un état.</p>
<p>Le code est, pour résumer, un ensemble d’instructions (à la manière d’une recette). Mais le code seul ne permet pas de faire grand-chose, il lui faut de la matière première à traiter avec ces instructions.</p>
<p>L’état est cette matière première. L’état permet de stocker les valeurs des variables à un instant donné. Quant aux variables, ce ne sont en réalité que des alias pour les espaces mémoire qui contiennent ces valeurs.</p>
<p>Reformulons : l’instance d’un module combine le code (la liste des instructions) avec un état (l’ensemble des valeurs des variables).</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/06_module_instance.png" title="Une instance de module combinant du code et un état"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.06_module_instance_m.png" style="margin: 0 auto; display: block;" title="Une instance de module combinant du code et un état"/></a></p>
<p>Il nous faut donc une instance de module pour chaque module. Le processus de chargement d’un module part du point d’entrée fourni par le fichier jusqu’à avoir un graphe complet avec l’ensemble des instances de module nécessaires.</p>
<p>Pour les modules ES, ce processus se décompose en trois étapes :</p>
<ol>
<li>Construction — le moteur trouve, télécharge et analyse l’ensemble des fichiers pour le convertir en <em>module records</em>.</li>
<li>Instanciation — le moteur détermine les zones mémoire dans lesquelles il va placer les valeurs exportées (il ne remplit pas ces zones pour l’instant). Ainsi, les exports et les imports peuvent pointer vers ces zones mémoire. C’est ce qu’on appelle aussi l’édition de lien ou <em>linking</em>.</li>
<li>Évaluation — le code est exécuté afin de remplir ces espaces mémoire avec les valeurs réelles des variables.</li>
</ol>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/07_3_phases.png" title="Une illustration des trois phases : la construction qui part d'un fichier JS afin de construire plusieurs module records ; l'instanciation qui relie ces records et l'évaluation qui exécute le code."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.07_3_phases_m.png" style="margin: 0 auto; display: block;" title="Une illustration des trois phases : la construction qui part d'un fichier JS afin de construire plusieurs module records ; l'instanciation qui relie ces records et l'évaluation qui exécute le code."/></a></p>
<p>On peut entendre dire que les modules ES sont <em>asynchrones</em>. En fait, cette asynchronicité vient du fait que chacune de ces trois étapes peut être exécutée séparément.</p>
<p>Cela signifie que la spécification introduit un niveau d’asynchronicité qui n’existait pas avec les modules CommonJS. Nous y reviendrons ensuite, mais il faut retenir qu’avec CJS, un module et ses dépendances sont chargées, instanciées et évaluées d’un coup, sans interruption entre ces étapes.</p>
<p>Ceci étant dit, les étapes individuelles ne sont pas nécessairement asynchrones et elles peuvent isolément être exécutées de façon synchrone. Cela dépend du chargement. En fait, la spécification pour les modules ES ne décrit pas l’intégralité du processus. Celui-ci se découpe en deux moitiés dont chacune est décrite par une spécification différente.</p>
<p><a href="https://tc39.github.io/ecma262/#sec-modules">La spécification pour les modules ES</a> indique comment analyser les fichiers pour les convertir en <em>module records</em> et comment instancier puis évaluer le module. Toutefois, elle ne dit rien de la façon dont on doit récupérer les fichiers pour commencer.</p>
<p>C’est au chargeur (<em>loader</em>) de récupérer les fichiers. Or, le comportement de ce chargeur est décrit dans une autre spécification. Pour les navigateurs, la spécification utilisée est <a href="https://html.spec.whatwg.org/#fetch-a-module-script-tree">la spécification HTML</a>. Pour d’autres plates-formes, on pourrait tout à fait avoir d’autres chargeurs qui devraient également être spécifiés.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/07_loader_vs_es.png" title="Deux personnages : celui de gauche représente la spécification qui définit comment charger des modules (c'est-à-dire la spécification HTML) et celui de droite représente la spécification des modules ES."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.07_loader_vs_es_m.png" style="margin: 0 auto; display: block;" title="Deux personnages : celui de gauche représente la spécification qui définit comment charger des modules (c'est-à-dire la spécification HTML) et celui de droite représente la spécification des modules ES."/></a></p>
<p>Le chargeur contrôle exactement la façon dont les modules sont chargés. Il appelle les méthodes associées aux modules ES : <code> ParseModule</code>, <code>Module.Instantiate</code> et <code>Module.Evaluate</code>. On pourrait le comparer à un marionnettiste qui tire les ficelles du moteur JavaScript.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/08_loader_as_puppeteer.png" title="Le personnage du chargeur agit comme un marionnettiste tirant les ficelles du personnage représentant la spécification des modules ES."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.08_loader_as_puppeteer_m.png" style="margin: 0 auto; display: block;" title="Le personnage du chargeur agit comme un marionnettiste tirant les ficelles du personnage représentant la spécification des modules ES."/></a></p>
<p>Étudions maintenant en détail chacune de ces trois étapes.</p>
<h3>Construction</h3>
<p>La phase de construction se décompose en trois sous-étapes :</p>
<ol>
<li>Déterminer où télécharger le fichier contenant le module (aussi appelée la résolution)</li>
<li>Récupérer le fichier (en le téléchargeant via une URL ou en le chargeant depuis le système de fichier)</li>
<li>Analyser le fichier et le convertir en un <em>module record</em></li>
</ol>
<h4>Trouver et récupérer le fichier</h4>
<p>C’est le chargeur qui va déterminer l’emplacement du fichier et le télécharger. Pour commencer, il lui faut un point d’entrée. En HTML, ce point d’entrée est fourni par le document indiqué via un élément <code><script></code>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/08_script_entry.png" title="Un élément \<script\> avec un attribut type=module et un attribut src contenant une URL. Cette URL pointe vers le fichier qui est le point d'entrée"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.08_script_entry_m.png" style="margin: 0 auto; display: block;" title="Un élément \<script\> avec un attribut type=module et un attribut src contenant une URL. Cette URL pointe vers le fichier qui est le point d'entrée"/></a></p>
<p>Mais comment faire pour trouver les modules suivants, ceux dont dépend <code>main.js</code> ?</p>
<p>C’est ici qu’intervient l’instruction <code>import</code>. Une partie de cette instruction est l’indicateur du module (<em>module specifier</em>). Cette partie permet au chargeur de connaître l’emplacement du prochain module.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/09_module_specifier.png" title="Une instruction import avec une URL en fin de ligne qui est identifiée comme indicateur du module"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.09_module_specifier_m.png" style="margin: 0 auto; display: block;" title="Une instruction import avec une URL en fin de ligne qui est identifiée comme indicateur du module"/></a></p>
<p>On notera ici qu’il y a une différence entre Node et les navigateurs pour ces indicateurs. Chaque environnement hôte dispose de sa propre méthode pour interpréter la chaîne de caractères fournissant l’indicateur. Pour cela, chaque plateforme utilise un algorithme de résolution qui lui est propre. À l’heure actuelle, certains indicateurs de module qui fonctionnent avec Node ne fonctionneront pas dans le navigateur (<a href="https://github.com/domenic/package-name-maps">des pistes sont à l’étude pour résoudre ce problème</a>).</p>
<p>Jusqu’à ce que ce problème soit résolu, les navigateurs acceptent uniquement des URL comme indicateurs de module. C’est le fichier vers lequel pointe cette URL qui sera chargé.</p>
<p>Toutefois, la récupération des différents modules ne se fait pas simultanément. On ne peut pas savoir les dépendances dont on a besoin tant qu’on a pas analysé le contenu du module et on ne peut pas analyser le fichier tant qu’on ne l’a pas récupéré.</p>
<p>Cela signifie qu’il faut progresser niveau par niveau : analyser le premier fichier, déterminer ses dépendances, trouver ces dépendances, les charger et ainsi de suite.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/10_construction.png" title="Un diagramme qui illustre un fichier récupéré puis analysé puis deux dépendances récupérées et analysées"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.10_construction_m.png" style="margin: 0 auto; display: block;" title="Un diagramme qui illustre un fichier récupéré puis analysé puis deux dépendances récupérées et analysées"/></a></p>
<p>S’il fallait que le <em>thread</em> principal patiente le temps que chacun de ces fichiers soit téléchargé, de nombreuses tâches s’ajouteraient à la queue.</p>
<p>En effet, lorsqu’on travaille dans un navigateur, le téléchargement dure longtemps.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/11_latency.png" title="Un graphe illustrant les durées d'accès et de téléchargement si un cycle processeur durait 1 seconde : l'accès à la mémoire principale prendrait 6 minutes et la récupération d'un fichier provenant d'un serveur à l'autre extrémité des États-Unis prendrait 4 ans."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.11_latency_m.png" style="margin: 0 auto; display: block;" title="Un graphe illustrant les durées d'accès et de téléchargement si un cycle processeur durait 1 seconde : l'accès à la mémoire principale prendrait 6 minutes et la récupération d'un fichier provenant d'un serveur à l'autre extrémité des États-Unis prendrait 4 ans."/></a></p>
<p><em>Illustration basée sur <a href="https://twitter.com/srigi/status/917998817051541504">ce graphique</a>.</em></p>
<p>Bloquer le <em>thread</em> principal de cette façon ralentirait considérablement une application qui utilise les modules. C’est pour cette raison que la spécification pour les modules ES découpe l’algorithme en plusieurs phases. Placer la construction à part permet aux navigateurs de récupérer les fichiers et de construire le graphe des modules avant de s’attaquer à la tâche, synchrone, d’instanciation.</p>
<p>Cette approche (de découpe d’algorithme) est une des différences fondamentales entre les modules ES et les modules CommonJS.</p>
<p>CommonJS peut gérer les choses différemment, car les fichiers proviennent du système de fichier et l’accès à ces ressources prend moins de temps que de les télécharger via Internet. Cela signifie que Node peut bloquer le <em>thread</em> principal pendant qu’il charge le fichier. Lorsque le fichier est chargé, on peut directement l’instancier et l’évaluer (encore une fois, ces phases ne sont pas distinctes avec CommonJS). Cela signifie également qu’on parcourt tout l’arbre des dépendances, qu’on les charge, qu’on les instancie et qu’on les évalue avant de renvoyer l’instance du module.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/12_cjs_require.png" title="Un diagramme illustrant l'évaluation d'un module Node jusqu'à l'instruction require puis suivant le chargement synchrone et l'évaluation du module et de ses dépendances"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.12_cjs_require_m.png" style="margin: 0 auto; display: block;" title="Un diagramme illustrant l'évaluation d'un module Node jusqu'à l'instruction require puis suivant le chargement synchrone et l'évaluation du module et de ses dépendances"/></a></p>
<p>L’approche adoptée par CommonJS implique différentes choses que nous verrons ensuite. Entre autres, cela signifie qu’avec Node et les modules CommonJS, on peut utiliser des variables dans l’indicateur du module. On exécute l’ensemble du code du module (y compris l’instruction <code>require</code>) avant d’aller consulter le prochain module. Ainsi, la variable utilisée aura une valeur lorsqu’on passera à la résolution de module.</p>
<p>En revanche, avec les modules ES, tout le graphe des dépendances doit être construit avant l’évaluation. Aussi, on ne peut pas avoir de variable au sein des identificateurs, car ces variables n’ont pas encore de valeurs.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/13_static_import.png" title="Une instruction require utilisant une variable ne pose pas de problème. Par contre, on ne peut pas utiliser de variable dans une instruction import."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.13_static_import_m.png" style="margin: 0 auto; display: block;" title="Une instruction require utilisant une variable ne pose pas de problème. Par contre, on ne peut pas utiliser de variable dans une instruction import."/></a></p>
<p>Cependant, il est parfois utile d’utiliser des variables pour les chemins des modules. On peut, par exemple, vouloir charger un module ou un autre en fonction de l’état du code ou de l’environnement dans lequel on exécute le code.</p>
<p>Une proposition, <a href="https://github.com/tc39/proposal-dynamic-import"><em>dynamic import</em></a>, vise à rendre cela possible et permettrait d’écrire une instruction <code>import</code> de la forme</p>
<pre><code>import(`${chemin}/toto.js`).
</code></pre>
<p>Pour cela, chaque fichier chargé grâce à <code>import()</code> est considéré comme un point d’entrée pour un autre graphe. Le module importé dynamiquement devient ainsi une racine d’un nouveau graphe, traité séparément.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/14dynamic_import_graph.png" title="Les graphes de deux modules avec une dépendance entre eux, étiquetée avec une instruction import dynamique."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.14dynamic_import_graph_m.png" style="margin: 0 auto; display: block;" title="Les graphes de deux modules avec une dépendance entre eux, étiquetée avec une instruction import dynamique."/></a></p>
<p>Pour un graphe donné, on aura un cache contenant les instances de ces modules. Aussi, si un module A est présent dans deux graphes, on aura deux caches avec deux instances. Pour chaque module d’une portée globale, il n’y aura qu’une seule instance du module.</p>
<p>Charger une seule instance pour chaque module minimise le travail du moteur JavaScript : le module n’est récupéré qu’une seule fois même lorsque plusieurs modules dépendent de ce premier (une des raisons pour la mise en cache des modules, nous en verrons une autre dans le détail consacré à la phase d’évaluation).</p>
<p>Le chargeur gère ce cache avec ce qu’on appelle <a href="https://html.spec.whatwg.org/multipage/webappapis.html#module-map">la carte des modules (<em>module map</em>)</a>. Chaque portée globale maintient le registre de ses modules dans une carte distincte.</p>
<p>Lorsque le chargeur récupère une URL, il place cette URL dans la carte des modules et note qu’il est en train de récupérer le fichier. Il envoie ensuite la requête puis démarre la récupération du prochain fichier.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/15_module_map.png" title="Le personnage du chargeur qui renseigne le tableau de la carte des modules avec l'URL et avec le mot "fetching" pour indiquer qu'il la récupère."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.15_module_map_m.png" style="margin: 0 auto; display: block;" title="Le personnage du chargeur qui renseigne le tableau de la carte des modules avec l'URL et avec le mot "fetching" pour indiquer qu'il la récupère."/></a></p>
<p>Que se passe-t-il lorsqu’un autre module dépend du même fichier ? Le chargeur vérifiera la présence de l’URL dans la carte. Si celle-ci est déjà en cours de récupération, il passera à l’URL suivante.</p>
<p>La carte des modules ne sert pas uniquement à indiquer les modules qui sont en train d’être récupérés, elle joue également le rôle de cache pour les modules comme nous le verrons par la suite.</p>
<h4>L’analyse (<em>parsing</em>)</h4>
<p>Nous avons désormais récupéré ce fichier et il faut le convertir en <em>module record</em> afin que le navigateur comprenne les différentes partie du module.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/25_file_to_module_record.png" title="Un diagramme illustrant l'analyse du fichier main.js pour le convertir en module record"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.25_file_to_module_record_m.png" style="margin: 0 auto; display: block;" title="Un diagramme illustrant l'analyse du fichier main.js pour le convertir en module record"/></a></p>
<p>Une fois le <em>module record</em> créé, il est placé dans la carte des modules. De cette façon, dès que le module est demandé, le chargeur peut le récupérer directement depuis la carte.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/25_module_map.png" title="Les cellules de la carte des modules, auparavant marquées avec "fetching", sont progressivement remplies avec des module records"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.25_module_map_m.png" style="margin: 0 auto; display: block;" title="Les cellules de la carte des modules, auparavant marquées avec "fetching", sont progressivement remplies avec des module records"/></a></p>
<p>Précisons un léger détail qui n’est peut-être pas si léger. Tous les modules sont analysés comme s’ils utilisaient <code>"use strict"</code> au début. D’autres différences (par rapport à l’analyse des scripts « classiques ») sont également présentes : le mot-clé <code>await</code> est réservé au niveau le plus haut d’un module ; la valeur de <code>this</code> vaut <code>undefined</code>.</p>
<p>Cette différence d’analyse est aussi appelée <em>parse goal</em>. Si on analyse le même fichier pour un autre usage, on pourrait obtenir un résultat différent. Aussi, avant l’analyse, il est nécessaire de savoir si on analyse un module ou non.</p>
<p>Pour les navigateurs, c’est plutôt facile. Il suffit de placer l’attribut <code>type="module"</code> dans la balise <code>script</code>. Le navigateur pourra alors savoir que le fichier doit être analysé comme un module. Au sein des scripts, seuls les modules peuvent être importés, le navigateur sait donc que tout ce qui est importé doit être analysé comme un module.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/26_parse_goal.png" title="Le navigateur détermine que main.js est un module, car la valeur de l'attribut type de la balise script l'indique. Pour counter.js, c'est aussi un module car il est importé"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.26_parse_goal_m.png" style="margin: 0 auto; display: block;" title="Le navigateur détermine que main.js est un module, car la valeur de l'attribut type de la balise script l'indique. Pour counter.js, c'est aussi un module car il est importé"/></a></p>
<p>Pour Node, c’est une autre histoire. Il n’y a pas d’éléments HTML et on ne peut donc pas utiliser un attribut <code>type</code>. Une des solutions utilisées par la communauté a été d’utiliser l’extension <code>.mjs</code>. Avec cette extension, on indique à Node que le fichier est un module. Vous pourrez lire certaines discussions où cette extension est un <em>signal</em> pour le <em>parse goal</em>. La discussion à ce sujet est toujours en cours et le <em>signal</em> qui sera utilisé par la communauté Node n’est pas encore fixé.</p>
<p>Dans tous les cas, le chargeur déterminera s’il faut analyser le fichier comme un module ou non. Si c’est un module et que celui-ci a des imports, le chargeur recommencera le processus jusqu’à ce que l’ensemble des fichiers ait été récupéré et analysé.</p>
<p>Et voilà ! Au début de cette étape, nous avions un fichier comme point d’entrée et maintenant nous avons un ensemble de <em>module records</em>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/27_construction.png" title="Un fichier JS à gauche avec trois module records analysés sur la droite pour modéliser le résultat de la phase de construction"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.27_construction_m.png" style="margin: 0 auto; display: block;" title="Un fichier JS à gauche avec trois module records analysés sur la droite pour modéliser le résultat de la phase de construction"/></a></p>
<p>La prochaine étape consiste à instancier ce module et à lier toutes ces instances ensembles.</p>
<h3>L’instanciation</h3>
<p>Comme évoqué plus haut, une instance combine du code avec un état. Cet état est en mémoire. Aussi, l’étape d’instanciation concernera principalement la mémoire.</p>
<p>Pour commencer, le moteur JavaScript crée un environnement pour le module. Cet environnement va gérer les variables pour le <em>module record</em>. Ensuite, il trouve les zones mémoire où placer l’ensemble des exports. C’est l’environnement du module qui maintiendra le registre d’association entre les zones mémoire et les exports.</p>
<p>Ces zones mémoire n’ont pas encore de valeur à l’intérieur. Ce n’est qu’après l’évaluation que les valeurs seront renseignées. Seule une exception échappe à cette règle : les déclarations de fonction sont initialisées pendant cette phase afin de faciliter la phase d’évaluation.</p>
<p>Pour instancier le graphe des modules, le moteur effectue un parcours postfixe (en profondeur) de l’arbre (<em>depth first post-order traversal</em>). Cela signifie qu’il commence par parcourir le bas de l’arbre (les dépendances qui n’ont aucune dépendance) et traite leurs exports.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/30_live_bindings_01.png" title="Illustration d'une colonne de mémoire vie au milieu. Les module records pour l'affichage et le comptage sont câblés à des zones mémoire sur cette colonne"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.30_live_bindings_01_m.png" style="margin: 0 auto; display: block;" title="Illustration d'une colonne de mémoire vie au milieu. Les module records pour l'affichage et le comptage sont câblés à des zones mémoire sur cette colonne"/></a>.</p>
<p>Une fois que le moteur a fini de câbler l’ensemble des exports du niveau inférieur, il monte d’un niveau pour câbler les imports du module correspondant.</p>
<p>On notera qu’un export et un import pointent vers le même emplacement en mémoire. Commencer par câbler les exports garantit la capacité à connecter les imports aux exports associés.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/30_live_bindings_02.png" title="Le même diagramme que précédemment mais sur lequel les imports de main.js sont désormais câblés sur les zones mémoire où sont aussi câblés les exports"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.30_live_bindings_02_m.png" style="margin: 0 auto; display: block;" title="Le même diagramme que précédemment mais sur lequel les imports de main.js sont désormais câblés sur les zones mémoire où sont aussi câblés les exports"/></a></p>
<p>Il y a une ici une différence avec les modules CommonJS. En CommonJS, tout l’objet exporté est copié à l’export. Cela signifie que les valeurs (les nombres par exemple) sont exportées sous forme de copies.</p>
<p>Cela signifie que si le module qui exporte modifie une valeur plus tard, le module qui l’import ne verra pas ce changement.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/31_cjs_variable.png" title="Le diagramme avec la colonne mémoire au milieu, à gauche l'export CommonJS qui est câblé sur un espace contenant la valeur 5 et à droite l'import qui est câble sur un autre espace contenant la copie de la valeur 5"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.31_cjs_variable_m.png" style="margin: 0 auto; display: block;" title="Le diagramme avec la colonne mémoire au milieu, à gauche l'export CommonJS qui est câblé sur un espace contenant la valeur 5 et à droite l'import qui est câble sur un autre espace contenant la copie de la valeur 5"/></a></p>
<p>Les modules ES utilisent à la place des liaisons dynamiques (<em>live bindings</em>). Les deux modules (l’export et l’import) pointent vers le même emplacement en mémoire. Cela signifie que lorsque le module exportant modifie une valeur, cette modification est également présente dans le module important.</p>
<p>Les modules qui exportent des valeurs peuvent modifier ces valeurs à tout moment mais les modules qui importent des valeurs ne peuvent pas modifier les valeurs importées. Ceci étant dit, si un module importe un objet, il peut modifier les valeurs des propriétés de cet objet.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/30_live_bindings_04.png" title="Le module exportant peut modifier la valeur en mémoire mais le module qui importe la valeur échoue s'il essaie."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.30_live_bindings_04_m.png" style="margin: 0 auto; display: block;" title="Le module exportant peut modifier la valeur en mémoire mais le module qui importe la valeur échoue s'il essaie."/></a></p>
<p>Avec ces liaisons dynamiques, il est possible de câbler tous les modules sans exécuter aucun code. Comme on le verra ensuite, cela facilite l’évaluation lorsqu’on a des dépendances cycliques.</p>
<p>À la fin de cette étape, toutes les instances de modules pour les imports et les exports sont câblées aux zones mémoire.</p>
<p>Nous voilà alors capables d’évaluer le code et de remplir ces zones mémoire avec leurs valeurs.</p>
<h3>L’évaluation</h3>
<p>L’étape finale consiste à remplir ces zones mémoire. Le moteur JS s’attaque à cette tâche en exécutant le code « de plus haut niveau » (<em>top level</em>), c’est-à-dire le code qui est situé en dehors des fonctions.</p>
<p>En plus de remplir ces espaces en mémoire, l’évaluation du code peut déclencher certains effets de bord. Un module peut, par exemple, communiquer avec un serveur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/40_top_level_code.png" title="Un module avec du code situé en dehors des fonctions qui suivent. Ce code est marqué comme étant au plus haut niveau"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.40_top_level_code_m.png" style="margin: 0 auto; display: block;" title="Un module avec du code situé en dehors des fonctions qui suivent. Ce code est marqué comme étant au plus haut niveau"/></a></p>
<p>À cause de ces effets de bord, il peut être préférable de n’évaluer le module qu’une seule fois. Contrairement à l’édition de lien qui se produit pendant l’instanciation et qui peut être effectuée plusieurs fois en produisant toujours le même résultat, l’évaluation pourra produire des résultats différents selon le nombre de fois qu’on l’a appliquée.</p>
<p>C’est l’une des raisons à l’origine de la carte des modules. La carte des modules met en cache les modules et expose une URL canonique pour chacun des modules. Ainsi, on s’assure que chaque module est exécuté une seule fois. Comme avec l’instanciation, l’exécution est effectuée en suivant un parcours postfixe (en profondeur) de l’arbre.</p>
<p>Mais que se passe-t-il avec les cycles que nous avons évoqués plus tôt ?</p>
<p>Si on a une dépendance cyclique, on aura une boucle dans le graphe. Généralement, cette boucle est plutôt longue. Toutefois, afin de pouvoir expliquer le problème ici, nous allons utiliser un exemple simplifié avec une boucle plus courte.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/41_cyclic_graph.png" title="Un graphe de module complexe avec un cycle de 4 modules à gauche. Sur la droite, un cycle avec 2 modules."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.41_cyclic_graph_m.png" style="margin: 0 auto; display: block;" title="Un graphe de module complexe avec un cycle de 4 modules à gauche. Sur la droite, un cycle avec 2 modules."/></a></p>
<p>Voyons ce qui se serait passé avec des modules CommonJS. Tout d’abord, le module principal est exécuté jusqu’à l’instruction <code>require</code>. Ensuite, on charge le module <code>counter</code>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/41_cjs_cycle.png" title="Un module CommonJS avec une variable exportée depuis main.js après une instruction require pour charger counter.js (qui dépend de l'import)."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.41_cjs_cycle_m.png" style="margin: 0 auto; display: block;" title="Un module CommonJS avec une variable exportée depuis main.js après une instruction require pour charger counter.js (qui dépend de l'import)."/></a></p>
<p>Le module <code>counter</code> essaie alors d’accéder à la variable <code>message</code> provenant de l’object d’export. Cette variable n’ayant pas encore été initialisée dans le module principal, elle renvoie <code>undefined</code>. Le moteur JavaScript alloue de la mémoire pour la variable locale et indique <code>undefined</code> comme valeur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/42_cjs_variable_2.png" title="La colonne représentant la zone mémoire au milieu : aucun lien entre main.js et la mémoire mais un lien d'import entre counter.js et un emplacement mémoire qui contient la valeur undefined"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.42_cjs_variable_2_m.png" style="margin: 0 auto; display: block;" title="La colonne représentant la zone mémoire au milieu : aucun lien entre main.js et la mémoire mais un lien d'import entre counter.js et un emplacement mémoire qui contient la valeur undefined"/></a></p>
<p>L’évaluation continue jusqu’à la fin du code de plus haut niveau pour le module <code>counter</code>. On veut pouvoir voir si la valeur correcte de <code>message</code> y parvient (après que main.js a été évalué) et on utilise donc <code>setTimeout</code> pour patienter. L’évaluation reprend alors du côté de <code>main.js</code>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/43_cjs_cycle.png" title="counter.js rend le contrôle à main.js, qui peut finir son évaluation"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.43_cjs_cycle_m.png" style="margin: 0 auto; display: block;" title="counter.js rend le contrôle à main.js, qui peut finir son évaluation"/></a></p>
<p>La variable <code>message</code> est initialisée et ajoutée à un emplacement mémoire mais aucun lien n’est créé entre cet emplacement et celui utilisé précédemment. Aussi, la valeur utilisée dans <code>counter</code> restera (indéfiniment) <code>undefined</code>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/44_cjs_variable_2.png" title="main.js envoie son export en mémoire et crée une connexion : la bonne valeur est bien inscrite en mémoire. Toutefois, counter.js continue de pointer vers le premier emplacement qui contient toujours undefined."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.44_cjs_variable_2_m.png" style="margin: 0 auto; display: block;" title="main.js envoie son export en mémoire et crée une connexion : la bonne valeur est bien inscrite en mémoire. Toutefois, counter.js continue de pointer vers le premier emplacement qui contient toujours undefined."/></a></p>
<p>Si les exports avaient été créés avec des liaisons dynamiques, le module <code>counter</code> aurait fini par voir arriver la bonne valeur pour <code>message</code>. En effet, après que l’évaluation de <code>main.js</code> a eu lieu et remplit la valeur, une fois que le <code>setTimeout</code> se déclenche, il utilise la bonne valeur.</p>
<p>La prise en charge de ces cycles a été une composante majeure lors de la conception des modules ES. C’est ce découpage en trois phases qui permet de gérer les dépendances cycliques.</p>
<h2>Quelle est la prise en charge actuelle des modules ES ?</h2>
<p>Avec la sortie de Firefox 60 en mai, l’ensemble des principaux navigateurs prendra en charge les modules ES par défaut. Node travaille également sur le sujet et un groupe de travail dédié se concentre sur les problèmes de compatibilité entre les modules CommonJS et les modules ES.</p>
<p>Cela signifie qu’on pourra utiliser la balise <code>script</code> avec <code>type=module</code> ainsi que des imports et des exports. D’autres fonctionnalités relatives aux modules sont dans les tuyaux. <a href="https://github.com/tc39/proposal-dynamic-import">La proposition concernant l’import dynamique</a> est au niveau 3 du processus de spécification. Il en va de même avec <a href="https://github.com/tc39/proposal-import-meta">import.meta</a> qui aidera à gérer certains cas d’utilisation pour Node.js. Enfin, <a href="https://github.com/domenic/package-name-maps">la proposition sur la résolution des modules</a> aidera à atténuer les différences entre les navigateurs et Node.js. En bref, le meilleur reste à venir.</p>
<h2>Remerciements</h2>
<p>Merci aux différentes personnes qui ont fourni leurs retours sur ce billet ou dont les écrits ou discussions ont aidé à sa rédaction : Axel Rauschmayer, Bradley Farias, Dave Herman, Domenic Denicola, Havi Hoffman, Jason Weathersby, JF Bastien, Jon Coppeard, Luke Wagner, Myles Borins, Till Schneidereit, Tobias Koppers, Yehuda Katz, les membres du groupe communautaire WebAssembly, les membres du groupe de travail pour les modules Node ainsi que les membres du TC39.</p>
<h2>À propos de <a href="http://code-cartoons.com/">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bricole avec JavaScript, WebAssembly, Rust, Servo et dessine des bandes dessinées à propos du code.</p>
Proposition de spécification pour l'API WebVR 1.0urn:md5:5ee1730bf78d1c88b8b82f0026428bec2016-03-05T12:55:00+01:002016-03-05T12:55:00+01:00sphinxDéveloppement WebHacksMozVRRéalité virtuelleVRWebVR<p><em>Cet article est une traduction de <a href="https://hacks.mozilla.org/2016/03/introducing-the-webvr-1-0-api-proposal/">Introducing the WebVR 1.0 API Proposal</a> écrit par Casey Yee et publié sur <a href="https://hacks.mozilla.org/">Hacks</a>.</em></p>
<p><em>Merci beaucoup à Ilphrin et Thegennok pour la traduction ! Merci goofy pour la relecture :)</em></p>
<hr />
<p>2016 s’annonce comme une année phare pour la réalité virtuelle. De nombreux produits de consommation autour de la réalité virtuelle sortiront sur le marché et beaucoup d’entreprises de logiciel s’apprêtent à prendre en charge ces nouveaux appareils. Le Web n’est pas en reste avec ce nouveau medium et les fournisseurs de navigateur s’affairent également. La croissance de WebVR s’est concentrée sur la création d’<a href="http://mozvr.com/#showcase">outils incroyables de visualisation</a> et d’<a href="http://mozvr.com/#developers">outils de création</a> pour le contenu de réalité virtuelle en ligne.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/VR_images-500x285.png" alt="WebVR Showcase" /></p>
<p><a href="https://twitter.com/MozillaVR">L’équipe de Mozilla qui s’occupe de la réalité virtuelle</a> a travaillé d’arrache-pied afin que Firefox permette de créer du contenu virtuel en ligne et permette de le consulter. Cette semaine (<em>NdT : la semaine du premier mars 2016</em>) est un jalon pour WebVR. Grâce au travail réalisé avec <a href="https://twitter.com/tojiro">Brandon Jones</a>, qui fait partie de l’équipe de Google Chrome, l’équipe Mozilla est fière d’annoncer la version 1.0 de la proposition de spécification pour l’API WebVR.</p>
<p>Les récentes avancées en réalité virtuelle et les retours de la communauté nous ont permis d’améliorer l’API afin de répondre aux besoins des développeurs.</p>
<p>Certaines de ces améliorations concernent :</p>
<ul>
<li>La gestion de l’affichage pour les appareils de réalité virtuelle</li>
<li>La capacité à circuler avec les liens entre différentes pages WebVR</li>
<li>Un schéma pour gérer les entrées des <a href="https://en.wikipedia.org/wiki/Six_degrees_of_freedom#Game_controllers">manettes à 6 degrés de libertés</a> (aussi appelées 6DoF pour <em>six degrees of freedom</em>)</li>
<li>La gestion des situations, qu’on soit assis ou debout</li>
<li>La pertinence de l’utilisation dans le cadre mobile ou dans le cadre d’un bureau</li>
</ul>
<p>Nous sommes ravis de pouvoir partager ces améliorations. La liste précédente ne représente qu’une petite partie de ce qui a été changé. Pour connaître tous les détails, vous pouvez lire <a href="https://mozvr.github.io/webvr-spec/">le brouillon de la spécification</a> et consulter <a href="http://blog.tojicode.com/2016/02/moving-towards-webvr-10.html">le billet de Brandon</a>.</p>
<p>Dans cet article, nous nous concentrerons sur une utilisation simple de l’API proposée. Quelques prérequis sont nécessaires comme des connaissances sur <a href="https://fr.wikipedia.org/wiki/Matrice_(math%C3%A9matiques)">les mathématiques appliquées aux matrices</a>. Vous pouvez aussi choisir de consulter <a href="https://aframe.io/">A-Frame</a> et <a href="https://github.com/borismus/webvr-boilerplate/">le modèle de base (<em>boilerplate</em>) WebVR</a> qui sont tous les deux construits sur cette API.</p>
<p>Avant d’aller plus loin, nous souhaiterions particulièrement remercier <a href="https://twitter.com/cvanw">Chris Van Wiemeersch</a> (Mozilla), <a href="https://twitter.com/kearwoodgilbert">Kearwood “Kip” Gilbert</a> (Mozilla), <a href="https://twitter.com/tojiro">Brandon Jones</a> (Google) et <a href="https://twitter.com/JustRogDigiTec">Justin Rogers</a> (Microsoft) qui ont contribué à la création de cette spécification.</p>
<h2>Feuille de route pour l’implémentation</h2>
<p>Nous prévoyons de livrer une implémentation stable de cette première version de l’API dans <a href="https://nightly.mozfr.org/">Firefox Nightly</a> au cours du premier semestre. Vous pouvez suivre <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1250244">Bugzilla</a> pour avoir accès à tous les détails. Vous pouvez aussi suivre les nouveautés liées à la prise en charge des plateformes sur <a href="https://iswebvrready.org/">iswebvrready.org</a>.</p>
<p>Envie de commencer aujourd’hui ? Les développeurs peuvent faire des tests en utilisant l’implémentation expérimentale de l’API, développée par Brandon Jones, présente sur <a href="https://drive.google.com/a/mozilla.com/folderview?id=0BzudLt22BqGRbW9WTHMtOWMzNjQ&usp=sharing#list">des versions de Chromium spécifiquement compilées</a> par Brandon.</p>
<p><a href="http://threejs.org/">three.js</a> et <a href="https://github.com/borismus/webvr-polyfill/">WebVR Polyfill</a> (utilisée pour <a href="https://github.com/borismus/webvr-boilerplate/">WebVR Boilerplate</a>) sont tous les deux ouverts aux <em>poules requêtes</em> afin de prendre en charge les dernières API.</p>
<h2>Les différents composants contribuant à la réalité virtuelle</h2>
<p>Voyons les différents composants qui jouent un rôle :</p>
<ul>
<li>L’appareil de réalité virtuelle sur lequel est affiché le contenu</li>
<li>La situation de l’utilisateur ; l’orientation et la position du casque dans l’espace</li>
<li>Les paramètres liés aux yeux qui définissent le champ de vision et la séparation stéréo.</li>
</ul>
<p>Voici le processus utilisé pour afficher du contenu dans un casque :</p>
<ul>
<li><code>navigator.getVRDisplays()</code> récupère un objet d’affichage pour la réalité virtuelle</li>
<li>On crée un élément <code><canvas></code> qui sera utilisé pour afficher le contenu</li>
<li>On utilise <code>VRDisplay.requestPresent()</code> pour fournir le canevas</li>
<li>On crée la boucle d’animation spécifique à l’appareil. Celle-ci sera utilisée pour afficher le contenu.
<ul>
<li><code>VRDisplay.getPose()</code> met à jour les informations liées à la situation de l’utilisateur.</li>
<li>On effectue les calculs et l’affichage</li>
<li>On utilise <code>VRDisplay.submitFrame()</code> pour indiquer lorsque le contenu du canevas est prêt à être présenté sur l’appareil.</li>
</ul></li>
</ul>
<p>Les sections qui suivent décrivent chacune de ces actions dans de plus amples détails.</p>
<h2>Travailler avec des appareils de réalité virtuelle</h2>
<p>Les appareils affichant des scènes de réalité virtuelle doivent respecter des conditions particulières pour <a href="https://fr.wikipedia.org/wiki/Images_par_seconde">le taux de rafraichissement</a>, <a href="https://en.wikipedia.org/wiki/Field_of_view">le champ de vision</a> et la présentation du contenu. Ces conditions sont donc gérées différemment des affichages classiques d’ordinateur.</p>
<h3>Lister les affichages de réalité virtuelle</h3>
<p>Afin de récupérer les affichages du navigateur qui peuvent être utilisés pour la réalité virtuelle, on utilisera la méthode <code>navigator.getVRDisplays()</code> qui renvoie une promesse (<code>Promise</code>) qui se résout en un tableau d’objets <code>VRDisplay</code>.</p>
<pre><code>navigator.getVRDisplays().then(function (displays) {
if (!displays.length) {
// WebVR est pris en charge, aucun affichage VRDisplays n'est trouvé
return;
}
// On gère les objets VRDisplay. (on utilise ici une variable globale
// au cas où on voudrait réutiliser l'objet autre part)
vrDisplay = displays.length[0];
}).catch(function (err) {
console.error("Impossible d'obtenir des affichages VRDisplays", err.stack);
});
</code></pre>
<p>Quelques éléments à garder en tête :</p>
<ul>
<li>Le casque de réalité virtuelle doit être branché et allumé afin que les appareils puissent être listés</li>
<li>Si vous ne disposez pas d’un casque de réalité virtuelle, vous pouvez simuler un appareil en utilisant <code>about:config</code> et en activant <code>dom.vr.cardboard.enabled</code> avec la valeur <code>true</code></li>
<li>L’appareil Cardboard VR (utilisé avec <a href="https://www.google.com/get/cardboard/">Google Cardboard</a>) pourra être listé pour les utilisateurs de <a href="https://nightly.mozilla.org/#Android">Firefox Nightly pour Android</a> ou de <a href="https://www.mozilla.org/fr/firefox/ios/">Firefox pour iOS</a>.</li>
</ul>
<h3>Créer une cible pour le rendu</h3>
<p>Pour déterminer la taille de la cible pour le rendu (c’est-à-dire la taille du canevas), on créera une cible de rendu suffisamment grande pour contenir les zones d’affichage (<em>viewports</em>) des deux yeux. Pour obtenir la taille (exprimée en pixels) de chaque œil, on pourra utiliser :</p>
<pre><code>// On utilisera 'left' pour l'œil gauche ou 'right' pour l'œil droit.
var eyeParameter = vrDisplay.getEyeParameters('left');
var width = eyeParameter.renderWidth;
var height = eyeParameter.renderHeight;
</code></pre>
<h3>Afficher le contenu dans le casque</h3>
<p>Pour afficher du contenu dans le casque, vous aurez besoin d’utiliser la méthode <code>VRDisplay.requestPresent()</code>. Cette méthode prend comme paramètre un élément WebGL <code><canvas></code> qui représente la surface à afficher pour la vision.
Afin de vérifier qu’il n’y a pas d’abus, le navigateur requiert un événement de la part de l’utilisateur pour passer une première fois en mode réalité virtuelle. Autrement dit, l’utilisateur doit choisir d’activer le mode réalité virtuelle. Pour cela nous plaçons un bouton « Passez en réalité virtuelle » et utilisons un gestionnaire d’événement pour le clic sur ce bouton.</p>
<pre><code>// Sélectionner l'élement WebGL canvas à partir du document
var webglCanvas = document.querySelector('#webglcanvas');
var enterVRBtn = document.querySelector('#entervr');
enterVRBtn.addEventListener('click', function () {
// Demande la présence du canevas WebGL sur l'affichage de réalité virtuelle
vrDisplay.requestPresent({source: webglCanvas});
});
// Pour finir la session et arrêter de présenter du contenu dans le casque.
vrDisplay.exitPresent();
</code></pre>
<h3>Un <code>requestAnimationFrame</code> pour chaque appareil</h3>
<p>Maintenant que le rendu cible est configuré et qu’on connaît les paramètres nécessaires pour le rendu et la présentation de la vue dans le casque, nous pouvons créer une boucle de rendu pour la scène.</p>
<p>Nous allons faire cela avec taux de rafraîchissement optimisé pour l’écran de réalité virtuelle. Nous utiliserons la fonction de rappel (<em>callback</em>) <code>VRDisplay.requestAnimationFrame</code> :</p>
<pre><code>var id = VRDisplay.requestAnimationFrame(onAnimationFrame);
function onAnimationFrame () {
// Boucle de rendu.
id = VRDisplay.requestAnimationFrame(onAnimationFrame);
}
// Pour quitter la boucle d'animation
VRDisplay.cancelRequestAnimationFrame(id);
</code></pre>
<p>Ce fonctionnement est analogue à celui qu’on a avec la fonction de rappel standard <code>window.requestAnimationFrame</code> que vous connaissez sûrement. Nous utilisons cette fonction pour appliquer des mises à jour de position, d’orientation et également pour rendre le contenu sur l’affichage de réalité virtuelle.</p>
<h3>Récupérer les informations de pose pour l’affichage de réalité virtuelle</h3>
<p>Nous avons besoins de récupérer l’orientation et la position du casque utilisant la méthode <code>VRDisplay.getPose()</code> :</p>
<pre><code>var pose = vrDisplay.getPose();
// Renvoie un quaternion.
var orientation = pose.orientation;
// Renvoie un vecteur à trois composantes
// qui décrit la position absolue.
var position = pose.position;
</code></pre>
<p>Notes :</p>
<ul>
<li>L’orientation et la position renvoyés valent <em>null</em> s’ils ne peuvent être déterminés.</li>
<li>Voir la spécification sur <a href="https://mozvr.github.io/webvr-spec/#interface-vrdisplaycapabilities"><code>VRStageCapabilities</code></a> et <a href="https://mozvr.github.io/webvr-spec/#interface-vrpose"><code>VRPose</code></a> pour plus d’informations.</li>
</ul>
<h3>Projeter une scène sur un affichage de réalité virtuelle</h3>
<p>Pour un rendu stéréoscopique de bonne qualité, nous avons besoin d’informations sur les yeux, tels que l’offset (la distance entre les deux pupilles, aussi appelée <a href="https://en.wikipedia.org/wiki/Interpupillary_distance">IPD</a>) et le champ de vision (<em>FOV</em> pour <em>field of vision</em>).</p>
<pre><code>// On passe 'left' ou 'right' selon l'œil visé.
var eyeParameters = vrDisplay.getEyeParameters('left');
// Après avoir traduit les coordonnées grâce à VRPose,
// on les décale avec le décalage de l'œil
var eyeOffset = eyeParameters.offset;
// Ensuite on projette le contenu grâce à une matrice.
var eyeMatrix = makeProjectionMatrix(vrDisplay, eyeParameters);
// On applique la matrice eyeMatrix à notre vue.
// ...
/**
* On génère une matrice de projection
* @param {object} display - VRDisplay
* @param {number} eye - VREyeParameters
* @returns {Float32Array} 4×4 la matrice de projection
*/
function makeProjectionMatrix (display, eye) {
var d2r = Math.PI / 180.0;
var upTan = Math.tan(eye.fieldOfView.upDegrees * d2r);
var downTan = Math.tan(eye.fieldOfView.leftDegrees * d2r);
var rightTan = Math.tan(eye.fieldOfView.rightDegrees * d2r);
var leftTan = Math.tan(eye.fieldOfView.leftDegrees * d2r);
var xScale = 2.0 / (leftTan + rightTan);
var yScale = 2.0 / (upTan + downTan);
var out = new Float32Array(16);
out[0] = xScale;
out[1] = 0.0;
out[2] = 0.0;
out[3] = 0.0;
out[4] = 0.0;
out[5] = yScale;
out[6] = 0.0;
out[7] = 0.0;
out[8] = -((leftTan - rightTan) * xScale * 0.5);
out[9] = (upTan - downTan) * yScale * 0.5;
out[10] = -(display.depthNear + display.depthFar) / (display.depthFar - display.depthNear);
out[12] = 0.0;
out[13] = 0.0;
out[14] = -(2.0 * display.depthFar * display.depthNear) / (display.depthFar - display.depthNear);
out[15] = 0.0;
return out;
}
</code></pre>
<h3>Envoyer les images au casque</h3>
<p>La réalité virtuelle est optimisée pour réduire le temps entre le mouvement de l’utilisateur et l’affichage de la réaction dans le casque. Cette réactivité est très importante afin d’obtenir une expérience confortable (et qui ne donnera pas le mal de mer). Pour que cela soit possible, on pourra utiliser les méthodes <code>VRDisplay.getPose()</code> et <code>VRDisplay.submitFrame()</code>.</p>
<pre><code>// Ici on traite les opérations de rendu et les calculs qui ne dépendent pas de la pose
// ....
var pose = vrDisplay.getPose();
// Ici les opérations de rendu et de calcul qui dépendent de la position du casque.
// C'est ici qu'il faut appliquer les matrices de l'œil.
// On essaiera de minimiser les opérations effectuées ici.
vrDisplay.submitFrame(pose);
// Toutes les opérations effectuées sur la frame après l'envoi n'ont aucun impact sur la latence.
// C'est donc un endroit utile pour générer une autre vue (telle qu'un reflet).
// ...
</code></pre>
<p>La règle générale consiste à appeler <code>VRDisplay.getPose()</code> aussi tard que possible et <code>VRDisplay.submitFrame()</code> aussi tôt que possible.</p>
<h2>Démonstration, retours et ressources</h2>
<p>Vous cherchez une façon de commencer ? Voici <a href="https://toji.github.io/webvr-samples/">une liste d’exemples</a> qui utilisent l’API 1.0 de WebVR. N’hésitez pas à regarder les ressources listées ci-dessous.</p>
<h3>Surtout, n’hésitez pas à faire vos retours !</h3>
<p>Le développement de cette proposition d’API est une réponse directe à l’évolution des technologies liées à la réalité virtuelle, aux retours et aux discussions que nous avons eues avec la communauté. Cette étape représente un bon début, continuez à nous faire vos retours pour poursuivre ces améliorations !</p>
<p>Nous vous invitons à renseigner les <em>issues</em> et à proposer des <em>pull requests</em> sur <a href="https://github.com/MozVR/webvr-spec/">le dépôt GitHub de la spécification WebVR</a>.</p>
<h3>Quelques ressources</h3>
<ul>
<li><a href="https://iswebvrready.org/">Is WebVR Ready?</a></li>
<li><a href="http://webvr.info/">webvr.info</a></li>
<li><a href="https://developer.mozilla.org/fr/docs/Web/API/WebVR_API">La documentation MDN sur l’API WebVR</a></li>
<li><a href="http://mozvr.com/">MozVR.com</a></li>
<li><a href="https://aframe.io/">A-Frame</a></li>
<li><a href="https://github.com/borismus/webvr-polyfill/">WebVR Polyfill</a> et <a href="https://github.com/borismus/webvr-boilerplate/">WebVR Boilerplate</a></li>
</ul>
<h2>À propos de <a href="https://hacks.mozilla.org/author/kyeemozilla-com/">Casey Yee</a></h2>
<p>Casey travaille au sein de l’équipe WebVR de Mozilla et mène des recherches autour des technologies web liées à la réalité virtuelle haute performance.</p>
<p><style>
pre { white-space: pre;}
</style></p>
ES6 en détails : les modulesurn:md5:3f7f75504fdc246afa75a1a81f926b752015-08-22T11:45:00+02:002015-08-22T10:49:59+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/08/12/ES6-en-details-%3A-les-sous-classes-et-l-heritage" hreflang="fr" title="Les sous-classes et l'héritage - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/08/es6-in-depth-modules/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à Banban et Ilphrin pour la traduction et merci à goofy pour la relecture :) !</em></p>
<hr /> <p><em><a href="https://tech.mozfr.org/tag/ES6%20en%20d%C3%A9tails" hreflang="fr" title="Articles étiquetés ES6 en détails">ES6 en détails</a> 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é).</em></p>
<p>En 2007, lorsque j’ai intégré l’équipe Mozilla pour le moteur JavaScript, nous avions une blague récurrente : un programme JavaScript tient généralement sur une ligne et c’est tout.</p>
<p>C’était deux ans après le lancement de Google Maps. Peu avant, JavaScript était en grande partie utilisé pour valider des formulaires et il était à peu près certain qu’un gestionnaire <code><input onchange=…></code> tiendrait sur une ligne.</p>
<p>Les choses ont changé. Les tailles des projets JavaScript sont désormais démentielles et la communauté a développé des outils pour travailler à ce niveau. Un des éléments de base nécessaire est un système de modules : une façon de partager le code pour l’utiliser entre plusieurs fichiers et plusieurs dossiers – tout en garantissant que chaque partie de votre code puisse accéder à une autre lorsque c’est nécessaire – et en étant capable de charger tout ce code efficacement. Il était donc naturel que JavaScript se dote d’un système de modules. Plusieurs, en fait. Il y a également plusieurs gestionnaires de paquets : des outils qui permettent d’installer tous les logiciels et de gérer les dépendances de haut niveau. Vous pourriez penser qu’ES6, avec sa nouvelle syntaxe pour les modules, arrive un peu après la cavalerie.</p>
<p>Soit, aujourd’hui nous allons voir si ES6 ajoute quelque chose à ces systèmes existants et si oui ou non les futurs outils et standard pourront s’appuyer sur lui. Mais pour le moment, voyons à quoi ressemblent les modules ES6.</p>
<h2>Les bases</h2>
<p>Un module ES6 est un fichier contenant du code JavaScript. Il n’y a pas de mot-clé spécifique <code>module</code>, un module est lu comme un script. Il y a deux différences :</p>
<ul>
<li>Les modules ES6 sont considérés comme écrits en mode strict, même si vous n’écrivez pas <code>"use strict";</code></li>
<li>Vous pouvez utiliser <code>import</code> et <code>export</code> dans les modules.</li>
</ul>
<p>Parlons d’abord d’<code>export</code>. Par défaut, tout ce qui est déclaré dans un module lui est local. Si vous voulez que quelque chose de déclaré dans un module soit public, afin que les autres modules puissent l’utiliser, vous devez l’exporter. Il existe plusieurs façons de faire. La plus simple est d’utiliser le mot-clé <code>export</code>.</p>
<pre>
// kittydar.js - Trouve tous les chats d'une image.
// (Cette librairie existe vraiment et a été écrite
// par Heather Arthur, mais elle n'utilisait pas les
// modules, car nous étions en 2013)
export function detectCats(canvas, options) {
var kittydar = new Kittydar(options);
return kittydar.detectCats(canvas);
}
export class Kittydar {
//... plusieurs méthodes de traitement d'image ...
}
// Cette fonction auxiliaire n'est pas exportée.
function resizeCanvas() {
...
}
...
</pre>
<p>Et c’est vraiment tout ce dont vous avez besoin pour écrire un module ! Vous n’avez rien à mettre dans un <a href="https://en.wikipedia.org/wiki/Immediately-invoked_function_expression" hreflang="en" title="IIFE - Wikipédia">IIFE</a> ou une fonction de rappel (<em>callback</em>). Allez-y et déclarez simplement ce qui est nécessaire. Puisque le code est un module et non un script, toutes les déclarations seront encapsulées dans ce module et ne seront <em>pas</em> visibles de façon globale pour les autres scripts et modules. Il suffit d’exporter les déclarations qui forment l’API publique de votre module.</p>
<p>En dehors des exportations, le code d’un module est tout à fait normal. On peut utiliser des objets globaux comme <code>Object</code> et <code>Array</code>. Si votre module est lancé dans un navigateur, celui-ci pourra utiliser <code>document</code> et <code>XMLHttpRequest</code>.</p>
<p>Dans un autre fichier, on peut importer et utiliser la fonction <code>detectCats()</code> :</p>
<pre>
// demo.js - Démo de Kittydar
import {detectCats} from "kittydar.js";
function go() {
var canvas = document.getElementById("catpix");
var cats = detectCats(canvas);
drawRectangles(canvas, cats);
}
</pre>
<p>Pour importer plusieurs noms à partir d’un module, on écrira :</p>
<pre>
import {detectCats, Kittydar} from "kittydar.js";
</pre>
<p>Quand vous exécutez un module contenant une déclaration <code>import</code>, les modules qu’il importe sont chargés en premier, puis le corps de chaque module est exécuté selon un parcours en profondeur du graphe de dépendance, cela évite les cycles et permet de passer tout ce qui a déjà été exécuté.</p>
<p>Voilà les bases des modules. C’est aussi simple que ça. ;-)</p>
<h2>Listes d’export</h2>
<p>Plutôt que de marquer chaque fonctionnalité exportée, vous pouvez écrire une liste de tous les noms à exporter, entre accolades :</p>
<pre>
export {detectCats, Kittydar};
// pas besoin d'utiliser le mot-clé `export` ensuite
function detectCats(canvas, options) { ... }
class Kittydar { ... }
</pre>
<p>Une liste <code>export</code> ne doit pas nécessairement être placée en début de fichier, elle peut apparaître n’importe où tant qu’elle est dans la portée de plus haut niveau du module. Il est possible d’avoir plusieurs listes <code>export</code> ou bien de mélanger les listes <code>export</code> avec d’autres déclarations <code>export</code>. Une condition à respecter : un nom ne doit pas être exporté plus d’une fois.</p>
<h2>Renommage des imports et exports</h2>
<p>Il arrive parfois qu’un nom importé entre en collision avec un nom déjà utilisé par ailleurs. ES6 vous permet donc de renommer les valeurs importées :</p>
<pre>
// suburbia.js
// Ces deux modules exportent quelque chose appelé « flip ».
// Pour les importer tous les deux, nous devons en renommer au moins un.
import {pain as painGlace} from "igloo.js";
import {pain as painBlé} from "boulangerie.js";
...
</pre>
<p>De la même manière, vous pouvez renommer les éléments lorsque vous les exportez. Plutôt pratique si vous voulez exporter la même valeur avec deux noms différents, ce qui peut arriver :</p>
<pre>
// unlicensed_nuclear_accelerator.js
// – lecture continue de media sans DRM
// (bibliothèque qui n'existe pas mais
// devrait peut-être...)
function v1() { ... }function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
</pre>
<h2>Exports par défaut</h2>
<p>Le nouveau standard est conçu pour pouvoir opérer avec les modules CommonJS et AMD existants. Si, par exemple, vous avez un projet Node et qu’à un moment vous avez tapé <code>npm install lodash</code>, vous pouvez importer les fonctions individuelles de Lodash dans votre code ES6 :</p>
<pre>
import {each, map} from "lodash";
each([3, 2, 1], x => console.log(x));
</pre>
<p>Mais peut-être avez-vous l’habitide de voir <code>_.each</code> plutôt que <code>each</code> et souhaitez-vous continuer à l’écrire de cette façon ? Ou peut-être avez-vous envie d’utiliser <code>_</code> comme une fonction <a href="https://lodash.com/docs#_" hreflang="en" title="Documentation - Lodash">puisque c’est utile avec Lodash</a> ?</p>
<p>Pour cela, vous pouvez utiliser une syntaxe légèrement différente, en important le module sans les accolades.</p>
<pre>
import _ from "lodash";
</pre>
<p>Ce raccourci est équivalent à <code>import{default as _} from lodash";</code>. Tous les modules CommonJS et AMD sont présentés à ES6 avec des exports par défaut qui correspondent à ce que vous auriez obtenu en utilisant <code>require()</code> pour le module souhaité (autrement dit, l’objet <code>exports</code>).</p>
<p>Les modules ES6 sont conçus pour vous permettre d’exporter plusieurs choses. Toutefois, pour les modules CommonJS existants, l’export par défaut sera tout ce que pourrez obtenir. Par exemple, à l’heure où est écrit cet article, le célèbre paquet <code><a href="https://github.com/Marak/colors.js" hreflang="en" title="colors.js - Marak - GitHub">colors</a></code> n’est pas <em>particulièrement</em> supporté par ES6. C’est un ensemble de modules CommonJS, comme la plupart des paquets npm et vous pouvez l’utilisez directement dans votre code ES6.</p>
<pre>
// Équivalent ES6 de `var colors = require("colors/safe");
`import colors from "colors/safe";
</pre>
<p>Si vous voulez que votre module ES6 ait un export par défaut, c’est simple à faire. Il n’y a rien de spécial au sujet de l’export par défaut, c’est un export comme un autre, excepté qu’il est appelé <code>default</code>. Vous pouvez donc utiliser la syntaxe de renommage dont nous avons déjà parlé :</p>
<pre>
let monObjet = {
champ1: valeur1,
champ2: valeur2
};
export {monObjet as default};
</pre>
<p>Encore mieux, vous pouvez utiliser la notation raccourcie :</p>
<pre>
export default {
champ1: valeur1,
champ2: valeur2
};
</pre>
<p>Les mots-clés <code>export default</code> peuvent être suivis par n’importe quelle valeur : une fonction, une classe, un littéral objet, à vous de choisir</p>
<h2>Les objets module</h2>
<p>Désolé, ça commence à faire un peu long. Cela dit, JavaScript n’est pas un cas isolé. Pour certaines raisons, les systèmes de modules des différents langages ont tendance à avoir plein de petites fonctionnalités utilitaires un peu ennuyantes. Heureusement, il ne reste qu’un seul point à voir… enfin deux.</p>
<pre>
import * as vaches from "vaches";
</pre>
<p>Quand vous écrivez <code>import *</code>, vous importez un <em>objet d’espace de noms pour le module</em>. Les propriétés de cet objet sont les valeurs exportées par le module. Ainsi, si le module <code>vache</code> exporte une fonction appelée <code>meugle()</code>, après avoir importé <code>vache</code> de cette façon, vous pourrez écrire <code>vache.meugle()</code>.</p>
<h2>Agréger des modules</h2>
<p>Parfois, le module principal d’un paquet importera des paquets d’autres modules pour les exporter ensuite de façon unifiée. Pour simplifier ce genre de code, il existe un raccourci d’importation-exportation.</p>
<pre>
// monde.js - des saveurs de toute la planète
// importer "sri-lanka" et réexporter
// certains de ses exports
export {Thé, Cannelle} from "sri-lanka";
// importer "guinée-équatoriale" et réexporter
// certains de ses exports
export {Café, Cacao} from "guinée-équatoriale";
// importer "singapour" et exporter tous ses exports
export * from "singapour";
</pre>
<p>Chacune de ces instructions <code>export from</code> est similaire à une instruction <code>import from</code> suivie d’un <code>export</code>. À la différence d’un import « réel », cela n’ajoute pas les éléments réexportés à la portée du module. Il ne faut donc pas utiliser cette notation raccourcie si vous souhaitez utiliser <code>Thé</code> dans votre <code>monde.js</code>. Il ne sera pas accessible de cette façon.</p>
<p>Si un des noms exporté par <code>singapour</code> entrait en collision avec un autre élément exporté par ailleurs, cela entraînerait une erreur. Attention donc quand vous utilisez <code>export *</code>.</p>
<p>Pfiou, on en a enfin fini avec la syntaxe. Passons aux choses sérieuses.</p>
<h2>Qu’est-ce que fait <code>import</code> ?</h2>
<p>Et si je vous disais qu’<code>import</code> ne fait… <em>rien</em> ?</p>
<p>Vous ne seriez pas si crédule… Eh bien, me croiriez-vous si je vous disais que le standard, pour une grande partie, ne dit pas ce que fait <code>import</code> ? Et que c’est une bonne chose ?</p>
<p>En ce qui concerne le chargement des modules, <a href="http://www.ecma-international.org/ecma-262/6.0/index.html#sec-hostresolveimportedmodule" hreflang="en" title="Résolution d'un module importé - ECMAScript 2015">ES6 laisse carte blanche aux implémentations</a>. Le reste sur l’exécution des modules est quant à lui <a href="http://www.ecma-international.org/ecma-262/6.0/index.html#sec-toplevelmoduleevaluationjob" hreflang="en" title="Évaluation du module - ECMAScript2015">spécifié en détails</a>.</p>
<p>Grossièrement, lorsque vous demandez au moteur JS de lancer un module, il doit se comporter de la façon suivante, avec ces quatre étapes :</p>
<ol>
<li>Analyse (<em>parsing</em>) : l’implémentation lit le code source du module et vérifie la présence d’erreurs de syntaxe</li>
<li>Chargement : l’implémentation charge tous les modules importés (récursivement). C’est cette partie qui n’est pas encore standardisée.</li>
<li>Édition des liens (<em>linking</em>) : pour chaque module chargé, l’implémentation crée une portée pour ce module et la remplit avec les liaisons déclarées dans ce module, y compris celles qui utilisent des données importées d’autres modules. C’est cette partie qui fait que vous aurez une erreur si vous utilisez <code>import {gâteau} from "mensonge"</code>, mais que le module <code>"mensonge"</code> n’exporte rien qui s’appelle <code>gâteau</code>. Dommage, vous n’étiez vraiment pas loin d’avoir du code JavaScript fonctionnel… et un peu de gâteau.</li>
<li>Exécution (<em>runtime</em>) : pour finir, l’implémentation exécute les instructions présentes dans les corps de chacun des modules qui viennent d’être chargés. À ce moment, le processus d’import est déjà fini. Quand l’exécution atteint une ligne de code utilisant une valeur importée, il n’y a donc rien qui se produit.</li>
</ol>
<p>Vous voyez, je vous avais bien dit que <em>rien</em> ne se passait !</p>
<p>Et c’est là qu’on arrive à la partie intéressante. Il y a une astuce : puisque le système ne spécifie pas le fonctionnement du chargement et parce que vous pouvez deviner en avance en regardant les déclarations import dans le code source, une implémentation ES6 peut donc faire tout le travail lors d’une compilation pour empaqueter tous les modules dans un seul fichier et l’envoyer sur le réseau ! Certains outils comme <a href="http://www.2ality.com/2015/04/webpack-es6.html" hreflang="en" title="webpack - 2ality">webpack</a> utilisent cette technique.</p>
<p>Ça n’a l’air de rien comme ça mais c’est très important : charger des scripts via le réseau prend du temps, à chaque fois que vous allez chercher un script, il se peut qu’il ait besoin d’en importer une douzaine en plus. Un outil de chargement simpliste utiliserait beaucoup d’allers-retours réseau. Avec webpack, non seulement vous pouvez utiliser les modules ES6 dès aujourd’hui mais vous n’avez aussi aucune pénalité sur la performance lors de l’exécution.</p>
<p>En ce qui concerne le chargement des modules, une spécification détaillée était prévue et elle fut construite. Une des raisons pour lesquelles elle ne fait pas partie du standard final est qu’il n’y avait pas de consensus sur la façon de gérer l’empaquetage (<em>bundling</em>). J’espère que quelqu’un trouvera une solution parce que, comme nous allons le voir, le chargement des modules devrait vraiment être standardisé. L’empaquetage est trop délicieux pour qu’on puisse s’en passer.</p>
<h2>« Statique contre dynamique » ou encore « les règles et comment les casser »</h2>
<p>Alors que JavaScript est un langage dynamique, le système de modules que nous avons décrit jusqu’à présent est étonnament statique.</p>
<ul>
<li>Toutes les variantes d’<code>import</code> et d’<code>export</code> sont permises uniquement au plus haut niveau dans un module. Il n’existe pas d’<code>import</code> ou d’<code>export</code> conditionnel et on ne peut pas importer dans la portée d’une fonction.</li>
<li>Tous les identifiants exportés doivent être explicitement exportés par leurs noms dans le code source. Il n’est pas possible de programmer une boucle parcourant un tableau pour exporter un ensemble de noms en fonction de données.</li>
<li>Les objets module sont gelés. Il n’y a aucune façon d’ajouter une nouvelle fonctionnalité à un module comme avec les prothèses.</li>
<li><em>Toutes</em> les dépendances d’un module doivent être chargées, analysées et liées avant que le code du module ne puisse être exécuté. Il n’y a pas d’élément de syntaxe pour qu’un import puisse être chargé à la demande.</li>
<li>Il n’y a aucun moyen de récupérer suite à une erreur d’import. Une application pourrait avoir des centaines de modules qui la composent, si un seul échoue à charger, rien ne fonctionnera. Il est impossible d’importer dans un bloc <code>try/catch</code> (un avantage ici : le système est tellement statique que webpack peut détecter ces erreurs pour vous lors de la compilation).</li>
<li>Il n’y a pas de mécanisme pour permettre à un module d’exécuter du code avant que ses dépendances soient chargées. Cela signifie qu’un module n’a aucun contrôle sur la façon dont ses dépendances sont chargées.</li>
</ul>
<p>Ce système fonctionne plutôt bien si vous avez des besoins statiques. Mais ce n’est pas inimaginable que d’avoir quelques besoins particuliers de temps en temps, pas vrai ?</p>
<p>C’est pour ça que n’importe quel système de chargement de module possède une API avec laquelle vous pouvez programmer pour aller de pair avec la syntaxe ES6 statique <code>import</code>/<code>export</code>. Par exemple, <a href="http://webpack.github.io/docs/code-splitting.html" hreflang="en" title="API Webpack - GitHub">webpack inclut une API</a> qui vous permet de « découper » du code, de charger certains paquets de modules à la demande. Cette même API vous permet de passer outre la plupart des règles évoquées dans la liste ci-avant.</p>
<p>La <em>syntaxe</em> des modules ES6 est très statique et c’est bien car cela permet d’utiliser des outils puissants pour la compilation. Mais cette syntaxe statique a été conçue pour être combinée avec une API de chargement riche, dynamique et à venir.</p>
<h2>Quand puis-je utiliser les modules ES6 ?</h2>
<p>À l’heure actuelle, pour utiliser les modules, vous aurez besoin d’un transpileur tel que <a href="https://github.com/google/traceur-compiler#what-is-traceur" hreflang="en" title="Traceur - Google - GitHub">Traceur</a> ou <a href="http://babeljs.io/" hreflang="en" title="BabelJS">Babel</a>. Dans <a href="https://tech.mozfr.org/post/2015/06/21/ES6-en-details-%3A-Utiliser-ES6-des-aujourd-hui-avec-Babel-et-Broccoli" hreflang="fr" title="Utiliser ES6 dès aujourd'hui avec Babel et Broccoli - tech.mozfr.org">un article précédent de la série</a>, Gastón I. Silva a montré comment utiliser Babel et Broccoli pour compiler du code ES6. Suite à cet article, Gastón a construit <a href="https://github.com/givanse/broccoli-babel-examples/tree/master/es6-modules" hreflang="en" title="Exemple Babel/Broccoli avec modules - Givanse - GitHub">un exemple utilisant les modules ES6</a>. <a href="http://www.2ality.com/2015/04/webpack-es6.html" hreflang="en" title="Billet sur webpack et ES6 par Axel Raushmayer - 2ality">Ce billet</a> d’Axel Rauschmayer contient un exemple utilisant Babel et webpack.</p>
<p>Le système de modules ES6 a principalement été conçu par Dave Herman et Sam Tobin-Hochstadt qui, pendant plusieurs années, ont défendu les composants statiques du système contre tout ceux qui arrivaient pour se joindre à cette controverse (dont moi). Jon Coppeard est en train d’implémenter les modules dans Firefox. Des travaux supplémentaires sont en cours pour standardiser <a href="https://github.com/whatwg/loader" hreflang="en" title="Loader - WHATWG - GitHub">le chargement des modules</a>. La suite devrait se traduire par un ajout à HTML devant vraisemblablement ressembler à <code><script type="module"></code>.</p>
<p>Et voilà ES6.</p>
<p>Cette série fut tellement intéressante que je n’ai pas envie qu’elle s’arrête. Encore un épisode ? Comme ça, nous pourrons parler des points divers et variés de la spécification ES6 qui n’étaient pas suffisamment conséquents pour mériter leur propre article. Nous en profiterons pour évoquer le futur d’ECMAScript. À la semaine prochaine donc, pour la formidable conclusion d’ES6 en détails.</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les sous-classes et l'héritageurn:md5:9046d73ca7bc45dfffe4b915934348852015-08-13T18:30:00+02:002015-08-21T20:22:50+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/08/06/ES6-en-details-%3A-let-et-const" hreflang="fr" title="let & const - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/08/es6-in-depth-subclassing/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à Banban et Marine pour la traduction et merci à goofy pour la relecture :) !</em></p>
<hr /> <p><em>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é).</em></p>
<p>Deux semaines auparavant, nous avons vu <a href="https://tech.mozfr.org/post/2015/07/29/ES6-en-details-%3A-les-classes" hreflang="fr" title="ES6 en détails : les classes - tech.mozfr.org">le nouveau système de classes</a> ajouté avec ES6 et qui permet de gérer les cas simples pour créer des constructeurs d’objets. Nous avons vu comment écrire du code qui ressemble à :</p>
<pre>
class Cercle {
constructor(rayon) {
this.rayon = rayon;
Cercle.nbrDeCercles++;
};
static dessiner(cercle, canvas) {
// Code pour dessiner dans le Canvas
};
static get nbrDeCercles() {
return !this._count ? 0 : this._count;
};
static set nbrDeCercles(val) {
this._count = val;
};
surface() {
return Math.pow(this.rayon, 2) * Math.PI;
};
get rayon() {
return this._radius;
};
set rayon(radius) {
if (!Number.isInteger(radius))
throw new Error("Le rayon du cercle doit être un entier.");
this._radius = radius;
};
}
</pre>
<p>Cependant, comme certains l’ont relevé, nous n’avons pas eu le temps de parler de l’étendue des pouvoirs des classes ES6. Comme pour les langages basés sur les classes (C++ ou Java par exemple), ES6 gère également l'<em>héritage</em> : une classe peut en utiliser une autre comme « base » et ajouter des fonctionnalités supplémentaires qui lui sont propres. Allons voir ce que nous réserve cette nouvelle fonctionnalité.</p>
<p>Mais avant de parler des sous-classes et de l’héritage entre classes, ça peut faire du bien de réviser l’héritage des propriétés et la <em>chaîne, dynamique, des prototypes</em>.</p>
<h2>L’héritage en JavaScript</h2>
<p>Lorsqu’on crée un objet, on peut lui adjoindre des propriétés mais il hérite également des propriétés de ses prototypes. Certains développeurs JavaScript connaissent bien la méthode <code>Object.create</code> qui permet de faire tout ça facilement :</p>
<pre>
var proto = {
valeur: 4,
méthode() { return 14; }
}
var obj = Object.create(proto);
obj.valeur; // 4
obj.méthode(); // 14
</pre>
<p>De plus, si on ajoute des propriétés à <code>obj</code> qui ont les mêmes noms que celles de <code>proto</code>, les propriétés de <code>obj</code> <em>masqueront</em> celles de <code>proto</code> (NdT : en anglais, on parle de « <em>shadowing</em> »).</p>
<pre>
obj.valeur = 5;
obj.valeur // 5
proto.valeur; // 4
</pre>
<h2>Un héritage de classes simple</h2>
<p>Après ces légères révisions, voyons comment raccrocher la chaîne de prototypes à un objet créé via une classe. Encore un rappel : quand on crée une classe, on construit une nouvelle fonction qui correspond à la méthode du constructeur dans la définition de la classe et qui contient toutes les méthodes statiques. On crée aussi un objet qui sera le prototype de cette fonction créée et qui contiendra toutes les méthodes des instances. Pour créer une nouvelle classe qui hérite de l’ensemble des propriétés statiques, il faudra que l’objet fonction correspondant à cette nouvelle classe hérite de l’objet fonction de la classe parente. De la même façon, il faudra que l’objet prototype de la nouvelle fonction hérite de l’objet prototype de la classe parente afin que la nouvelle classe bénéficie des méthodes d’instances.</p>
<p>Cette description est un peu difficile à digérer, voyons ce que ça donne dans un exemple utilisant des éléments de syntaxe connus. Ensuite, nous ajouterons un élément syntaxique simple mais qui permettra de rendre le code plus agréable à lire.</p>
<p>Si on poursuit avec notre exemple précédent et qu’on considère une classe <code>Forme</code> qu’on veut décliner :</p>
<pre>
class Forme {
get couleur() {
return this._couleur;
}
set couleur(c) {
this._couleur = parseColorAsRGB(c);
this.modifApportée(); // on repeindra le canvas après
}
}
</pre>
<p>Lorsqu’on essaie d’écrire le code pour faire ça, on se confronte au même problème qu’on a eu dans le billet précédent sur les classes avec les propriétés statiques : il n’y a aucun élément syntaxique qui permette de changer le prototype d’une fonction lors de sa définition. Bien qu’on puisse résoudre ça à coup de <code>Object.setPrototypeOf</code>, cette approche est limitée en termes d’optimisation et de performances et on préfèrerait avoir un moyen de créer une fonction avec le prototype voulu.</p>
<pre>
class Cercle {
// Comme vu avant
}
// Rattacher les propriétés des instances
Object.setPrototypeOf(Cercle.prototype, Forme.prototype);
// Rattacher les propriétés statiques
Object.setPrototypeOf(Cercle, Forme);
</pre>
<p>C’est assez moche. La syntaxe des classes a été ajoutée afin qu’on puisse encapsuler toute la logique d’un objet à un seul endroit plutôt que d’avoir à « rattacher » des choses a posteriori. Java, Ruby et d’autres langages ont une façon de déclarer qu’une déclaration de classe est une sous-classe d’une autre, ça devrait également être le cas pour JavaScript. Pour cela, on peut utiliser le mot-clé <code>extends</code> et écrire :</p>
<pre>
class Cercle extends Forme {
// Comme vu avant
}
</pre>
<p>On peut mettre n’importe quelle expression après extends tant que celle-ci correspond à un constructeur valide avec une propriété prototype. On pourra donc mettre :</p>
<ul>
<li>une autre classe</li>
<li>des fonctions semblables à des classes provenant de frameworks gérant l’héritage</li>
<li>une fonction normale</li>
<li>une variable qui contient une fonction ou une classe</li>
<li>un accès à une propriété d’un objet</li>
<li>un appel de fonction</li>
</ul>
<p>On peut même utiliser <code>null</code> si on souhaite que les instances n’héritent pas d’<code>Object.prototype</code>.</p>
<h2>Des oiseaux ? Des avions ? Non… Ce sont les <code>super</code> propriétés !</h2>
<p>On peut donc faire des sous-classes, on peut hériter des propriétés et dans certains cas, nos méthodes masqueront (ou <em>surchargeront</em>) les méthodes qui auraient du être héritées. Comment faire quand on voudra contourner ce masque ?</p>
<p>Imaginons qu’on veuille écrire un sous-classe de la classe <code>Cercle</code> qui gère un homothétie avec un facteur donné. Pour cela, on pourrait écrire :</p>
<pre>
class CercleHomothétique extends Cercle {
get rayon() {
return this.facteur * super.rayon;
}
set rayon() {
throw new Error("Le rayon d'un CercleHomothétique est constant." +
"Modifier plutôt le facteur d'échelle.");
}
// Code pour gérer le facteur d'échelle
}
</pre>
<p>On remarque ici que l’accesseur pour le rayon utilise <code>super.rayon</code>. Ce nouveau mot-clé, <code>super</code>, permet d’utiliser les propriétés du prototype, y compris quand celles-ci sont surchargées. <code>super[expr]</code> fonctionne également. L’accès aux propriétés avec <code>super</code> peut être utilisé dans n’importe quelle fonction définie avec la syntaxe de définition d’une méthode. Même si ces méthodes sont utilisées en dehors de l’objet original, l’accès aux propriétés est toujours lié à l’objet sur lequel la méthode a été définie initialement. Autrement dit, si on stocke la méthode dans une nouvelle variable, cela ne changera pas le comportement de <code>super</code>.</p>
<pre>
var obj = {
toString() {
return "MonObjet : " + super.toString();
}
}
obj.toString(); // MonObjet : [object Object]
var a = obj.toString;
a(); // MonObjet : [object Object]
</pre>
<h2>Créer des sous-classes pour les objets natifs</h2>
<p>Avec tout ça, il est possible d’étendre les objets natifs offerts par le langage. Les structures de données natives ajoutent une richesse énorme à JavaScript, ce sera très utile de pouvoir créer de nouveaux types qui tirent parti de cette richesse. Cet aspect était une notion clé lors de la conception du système de sous-classes. Imaginons qu’on ait l’idée folle de créer un tableau versionné… On veut pouvoir effectuer des changements, les sauvegarder (« <em>commit</em> »), revenir à une version précédente (« <em>rollback</em> »). On peut utiliser l’objet <code>Array</code> et en créer une sous-classe :</p>
<pre>
class VersionedArray extends Array {
constructor() {
super();
this.history = [[]];
}
commit() {
// On sauvegarde les changements en historique
this.history.push(this.slice());
}
revert() {
this.splice(0, this.length, this.history[this.history.length - 1]);
}
}
</pre>
<p>Les instances de <code>VersionedArray</code> contiennent quelques propriétés importantes. Ce sont des instances d’<code>Array</code> avec l’ensemble des attributs utiles que sont <code>map</code>, <code>filter</code> et <code>sort</code>. <code>Array.isArray()</code> les considèrera comme des tableaux, même la propriété <code>length</code> sera automatiquement mise à jour pour ces instances. Encore mieux : les fonctions qui renvoient un nouveau tableau (par exemple <code>Array.prototype.slice()</code>) renverront bien un <code>VersionedArray</code> !</p>
<h2>Les constructeurs de sous-classes</h2>
<p>Dans la méthode <code>constructor</code> de l’exemple précédent, vous avez pu voir le <code>super()</code> utilisé. Quel est son rôle ?</p>
<p>Dans les modèles de classes traditionnels, les constructeurs sont utilisés pour initialiser l’état des instances de la classe. Chaque sous-classe qui suit est ensuite responsable de l’initialisation de l’état associé aux spécificités de la sous-classe. On veut pouvoir enchaîner ces appels afin que les sous-classes puissent partager ce code d’initialisation pour la classe qu’elles étendent.</p>
<p>Pour appeler un constructeur d’une classe parente, on utilise là encore le mot-clé <code>super</code> mais cette fois comme s’il s’agissait d’une fonction. Cette syntaxe n’est valide qu’au sein de méthodes <code>constructor</code> pour des déclarations de classes qui utilisent <code>extends</code>. Avec <code>super</code>, on peut donc réécrire notre classe <code>Forme</code> :</p>
<pre>
class Forme {
constructor(couleur) {
this._couleur = couleur;
}
}
class Cercle extends Forme {
constructor(couleur, rayon) {
super(couleur);
this.rayon = rayon;
}
// Comme précédemment
}
</pre>
<p>En JavaScript, on a l’habitude d’écrire des constructeurs qui agissent sur l’objet <code>this</code> pour installer des propriétés et initialiser l’état interne. Normalement, l’objet <code>this</code> est créé lorsqu’on appelle le constructeur avec <code>new</code>, comme si on appelait <code>Object.create()</code> sur la propriété <code>prototype</code> du constructeur. Cependant, certains objets natifs sont organisés différemment en interne. Les tableaux (<code>Array</code>) par exemple, sont organisés différemment en mémoire. Étant donné qu’on souhaite vouloir créer des sous-classes pour les types natifs, on laisse le constructeur le plus « haut » allouer l’objet <code>this</code>. Ainsi, si c’est un objet natif, on aura l’organisation voulue et si c’est un constructeur normal, on aura l’objet <code>this</code> par défaut qu’on attend.</p>
<p>Cela entraîne une bizarrerie dans la façon dont <code>this</code> est lié aux constructeurs des sous-classes. Avant d’avoir exécuté le constructeur de base et donc d’avoir pu allouer l’objet <code>this</code> : <strong>on n’a pas de valeur pour <code>this</code></strong>. Pour cette raison, tous les accès aux constructeurs de sous-classes lèveront une exception <code>ReferenceError</code> s’ils ont lieu avant l’appel au constructeur <code>parent</code> avec <code>super()</code>.</p>
<p>Comme on l’a vu dans le billet précédent sur les classes, il est possible de ne pas écrire de méthode constructeur pour une classe. C’est également valable pour les sous-classes et leurs constructeurs. Si on ne déclare pas de méthode constructor, cela sera équivalent à :</p>
<pre>
constructor(...args) {
super(...args);
}
</pre>
<p>Parfois, les constructeurs n’interagissent pas avec l’objet <code>this</code>. À la place, ils créent un objet d’une autre façon, l’initialisent et le renvoient directement. Si c’est le cas, il n’est pas nécessaire d’utiliser <code>super</code>. N’importe quel constructeur peut renvoyer un objet directement, qu’un constructeur parent ait été appelé ou non.</p>
<h2><code>new.target</code></h2>
<p>L’allocation effectuée par la classe la plus « haute » a une autre conséquence un peu étrange : parfois la classe la plus haute ne sait pas quel type d’objet allouer. Imaginons qu’on ait écrit un framework de gestion d’objets et qu’on ait une classe de base <code>Collection</code> dont certaines des sous-classes sont des tableaux (<code>Array</code>) et d’autres des tableaux associatifs (<code>Maps</code>). Au moment où on utilise le constructeur <code>Collection</code>, on est incapable de dire quelle sorte de sous-classe nous intéresse.</p>
<p>Puisqu’on peut avoir des sous-classes pour les types natifs, lorsqu’on exécute le constructeur natif, en interne, on doit connaître le prototype de la classe originale. Sans ça, on serait incapable de créer un objet avec les bonnes méthodes d’instances. Pour résoudre ce cas avec les « <code>Collections</code> », un nouvel élément de syntaxe a été ajouté à JavaScript pour exposer ces informations : la <em>méta-propriété</em> <code>new.target</code>. Elle correspond au constructeur qui a été appelé avec <code>new</code>. Lorsqu’on appelle une fonction avec <code>new</code>, <code>new.target</code> prendra la valeur de la fonction appelée. La valeur de <code>new.target</code> est transmise lorsqu’on utilise <code>super</code> au sein du constructeur.</p>
<p>Un exemple vaut mieux qu’un long discours pour expliquer :</p>
<pre>
class Toto {
constructor() {
return new.target;
}
}
class Truc extends Toto {
// This is included explicitly for clarity. It is not necessary
// to get these results.
constructor() {
super();
}
}
// Toto directement appelé, new.target correspond à Toto
new Toto(); // Toto
// 1) Truc directement appelé, new.target correspond à Truc
// 2) Truc appelle Toto via super(), new.target est toujours Truc
new Truc(); // Truc
</pre>
<p>Nous avons résolu le problème avec la <code>Collection</code> décrite ci-dessus, parce que le constructeur de la collection peut juste vérifier <code>new.target</code> et l’utiliser pour les sous-classes, puis déterminer quel type natif utiliser.</p>
<p><code>new.target</code> est valide dans n’importe quelle fonction. Si la fonction n’est pas appelée avec <code>new</code>, il vaudra <code>undefined</code>.</p>
<h2>Un mélange détonant</h2>
<p>En espérant que vous ayez survécu à ces nouvelles fonctionnalités. Merci de vous être accroché-e. Prenons un peu de temps pour se demander s’ils permettent de résoudre les problèmes correctement. De nombreux développeurs se sont demandé si c’était une bonne chose d’utiliser l’héritage pour coder une fonctionnalité. Vous pourriez croire que l’héritage n’est pas aussi efficace que <a href="https://en.wikipedia.org/wiki/Composition_over_inheritance" hreflang="en" title="Composition over inheritance - Wikipédia">la composition</a> pour construire des objets, ou que la propreté de cette nouvelle syntaxe ne vaut pas l’absence de flexibilité du design qui en résulte, en comparaison avec l’ancien modèle basé sur les prototypes. Il est indéniable que les mixins sont devenus le mode d’expression dominants pour créer des objets qui partagent du code de façon extensible. C’est le cas pour une bonne raison : ils fournissent une méthode facile permettant de partager du code sans relation avec le même objet, sans avoir à comprendre comment ces deux éléments sans relation peuvent s’intégrer dans la même structure d’héritage.</p>
<p>Il existe de nombreuses croyances sur ce sujet, mais je crois qu’il y a quelques aspects notables. Premièrement, l’ajout des classes comme une fonctionnalité du langage ne rend pas leur utilisation obligatoire. Deuxièmement, tout en étant aussi important, l’ajout des classes comme une fonctionnalité du langage ne signifie pas qu’elles sont toujours le meilleur moyen de résoudre les problèmes d’héritage ! En réalité, certains problèmes seront plus facilement résolus en utilisant l’héritage et les prototypes. En fin de compte, les classes sont juste un nouvel outil à votre disposition, ce n’est pas le seul outil que vous avez et ce n’est pes nécessairement le meilleur.</p>
<p>Si vous souhaitez continuer à utiliser les mixins, vous aurez besoin de classes qui puissent hériter de plusieurs sources. Malheureusement, ce serait assez bouleversant de modifier le modèle d’héritage maintenant. Pour cette raison, JavaScript n’implémente pas l’héritage multiple de classes. Ceci étant dit, il existe une solution hybride qui permet d’utiliser les mixins dans un framework basé sur les classes.</p>
<p>Les fonctions suivantes, par exemple, sont basées sur l’idiome <em>mixin extend</em> :</p>
<pre>
function mix(...mixins) {
class Mix {}
// On ajoute toutes les méthodes et accesseurs
// des mixins à la classe Mix.
for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if (key !== "constructor" && key !== "prototype" && key !== "name") {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
</pre>
<p>Cette fonction <code>mix</code> peut désormais être utilisée pour créer une classe parente composée. Il n’y a pas besoin de créer une relation d’héritage explicite entre les différents mixins. Si par exemple, on travaille sur un outil d’édition collaboratif et que les actions d’édition sont enregistrées et que le contenu de ces actions doit être sérialisé, on pourra utiliser la fonction <code>mix</code> pour définir la classe <code>DistributedEdit</code> :</p>
<pre>
class DistributedEdit extends mix(Loggable, Serializable) {
// Méthodes d'événements
}
</pre>
<p>C’est ainsi qu’on mélange les deux approches efficacement. C’est aussi facile de voir comment étendre ce modèle pour gérer des classes mixin qui possèdent des classes parentes : il suffit de passer la classe parente dans le mélange et que la classe renvoyée étende cette classe parente.</p>
<h2>Quand puis-je commencer à utiliser ces fonctionnalités ?</h2>
<p>OK, on a beaucoup parlé des sous-classes et de décliner les objets natifs, mais peut-on les utiliser dès maintenant ?</p>
<p>À peu près. Parmi les principaux navigateurs, Chrome possède la plupart des fonctionnalités évoquées aujourd’hui. En mode strict, vous devriez pouvoir faire tout ce que nous avons vu à part décliner <code>Array</code>. Les autres types natifs sont fonctionnels mais <code>Array</code> est un peu particulier et ce n’est donc pas une surprise que ça prenne du temps. En ce qui concerne Firefox, je suis en train d’implémenter ces fonctionnalités et espère atteindre le même objectif (tout sauf les déclinaisons d’<code>Array</code>) très bientôt. Le <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1141863" hreflang="en" title="Bug 1141863 - bugzilla.mozilla.org">bug 1141863</a> contient plus d’informations à ce sujet, il devrait arriver dans la version Nightly de Firefox d’ici quelques semaines.</p>
<p>Edge supporte <code>super</code> mais pas les sous-classes pour les types natifs. Safari ne supporte aucune de ces fonctionnalités.</p>
<p>Sur ce sujet, les transpileurs ne sont pas d’un grand secours. Ils permettent de créer des classes et d’utiliser <code>super</code> mais ils n’ont aucun moyen pour assister à la création de sous-classes des types natifs. En effet, il faut que le moteur JavaScript permette d’obtenir les instances de la classe de base pour les méthodes natives (par exemple quand on utilise <code>Array.prototype.splice</code> sur une classe).</p>
<p>Bon, ce fut un long billet ! La semaine prochaine, Jason Orendorff revient pour discuter du système des modules ES6 en détails.</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : let et consturn:md5:de4de3d7745830da65b5f6e249f989ba2015-08-07T18:30:00+02:002015-08-07T18:30:00+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/07/29/ES6-en-details-%3A-les-classes" hreflang="fr" title="Les classes - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à Banban et goofy pour la traduction et la relecture :) !</em></p>
<hr /> <p><em>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é).</em></p>
<p>La fonctionnalité dont je souhaiterais parler aujourd’hui est à la fois humble et étonnamment ambitieuse.
Lorsque Brendan Eich a conçu la première version de JavaScript en 1995, il s’est trompé sur de nombreux sujets, certains font toujours partie du langage : l’objet <code>Date</code>, la conversion automatique des objets en <code>NaN</code> lorsqu’on les multiplie… Malgré ça, il a visé juste sur plein d’aspects fondamentaux : les objets, les prototypes, les fonctions comme entités de premier rang, les portées lexicales, la mutabilité par défaut. Le langage repose sur de bonnes bases, meilleures que quiconque le pensait au début.</p>
<p>Cependant, c’est sur une décision de conception particulière de Brendan que porte l’article d’aujourd’hui. Une décision dont on peut dire, je pense, qu’il s’agit d’une erreur. Une toute petite erreur, subtile. Vous pouvez avoir utilisé le langage pendant des années sans même l’avoir remarquée. Mais cette erreur est importante car elle fait partie des « bonnes parties » de JavaScript (NdT : en raison des écueils évoqués avant, JavaScript est souvent découpé entre les « bad parts », à éviter, et les « good parts », meilleurs morceaux du langage).</p>
<p>Cette erreur est liée aux variables.</p>
<h2>Problème n°1 : les blocs ne forment pas de portées</h2>
<p>La règle semble anodine : <strong>la <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions#Fonctions_imbriqu%C3%A9es_et_fermetures" hreflang="fr" title="Portées - MDN">portée</a> d’une variable déclarée dans une fonction JS correspond au corps de la fonction dans son ensemble</strong>. Cela peut cependant avoir deux conséquences particulières qui font grincer des dents.</p>
<p>L’une est que la portée des variables déclarées dans les blocs ne correspond pas au bloc. C’est la fonction toute entière.</p>
<p>Vous n’aviez probablement jamais remarqué ça auparavant. J’ai bien peur que ce soit l’une des choses que désormais, vous ne pourrez plus oublier. Considérons un scénario où cela mène à un drôle de bug.</p>
<p>Disons que vous avez un bout de code existant qui utilise une variable nommée <code>t</code> :</p>
<pre>
function runTowerExperiment(tower, startTime) {
var t = startTime;
tower.on("tick", function () {
... code qui utilise t ...
});
... plus de code ...
}
</pre>
<p>Tout fonctionne parfaitement, jusqu’ici. Maintenant, vous voulez ajouter des mesures de vitesse d’une boule de bowling, donc vous ajoutez une petite condition <code>if</code> à la fonction de rappel interne.</p>
<pre>
function runTowerExperiment(tower, startTime) {
var t = startTime;
tower.on("tick", function () {
... code qui utilise t ...
if (bowlingBall.altitude() <= 0) {
var t = readTachymeter();
...
}
});
... plus de code ...
}
</pre>
<p>Oh mon dieu ! Vous avez inconsciemment ajouté une seconde variable appelée <code>t</code>. Maintenant, dans le « code qui utilise t », qui fonctionnait bien avant, <code>t</code> fait référence à la nouvelle variable interne <code>t</code> à la place de la variable externe existante.</p>
<p>La portée d’une variable en JavaScript est comme l’outil <em>Pot de peinture</em> dans Photoshop. Elle s’étend dans les deux directions depuis la déclaration, en avant et en arrière, et jusqu’à ce qu’elle rencontre une délimitation de fonction. La portée de cette variable <code>t</code> allant loin en arrière, elle a été créée dès que nous sommes entrés dans la fonction. C’est ce qu’on appelle la remontée des variables (<em>hoisting</em>). J’aime me représenter une petite grue codée, actionnée par le moteur JS, qui fait monter chacune des variables et fonctions tout en haut de la fonction englobante.</p>
<p>Après, la remontée des variables a aussi ses bons côtés. Sans elle, plein de techniques parfaitement convenables qui fonctionnent bien avec la portée globale ne marcheraient plus dans une <a href="https://en.wikipedia.org/wiki/Immediately-invoked_function_expression" hreflang="en" title="IIFE - Wikipédia">IIFE</a> (<em>Immediately-invoked function expression</em> pour « expression de fonction immédiatement invoquée »). Dans le cas présent cependant, la remontée entraîne un gros bug : tous vos calculs utilisant <code>t</code> renverront <code>NaN</code>. Ce sera difficile à retrouver, trop même, en particulier si votre code est plus complexe que ce petit exemple gentillet…</p>
<p>On a ajouté un nouveau bloc de code et ça a entraîné une erreur dans du code situé <em>avant</em> ce bloc. C’est vraiment étrange, on s’attendrait vraiment à ce que les effets précèdent les causes.</p>
<p>Enfin ce problème est plutôt léger en comparaison du deuxième lié à var.</p>
<h2>Problème n°2 : le partage excessif des variables dans les boucles</h2>
<p>En utilisant ce code, vous pouvez deviner ce qui va se produire, c’est totalement logique :</p>
<pre>
var messages = ["Coucou", "je suis une page web", "alert() c'est rigolo !"];
for (var i = 0; i < messages.length; i++) {
alert(messages[i]);
}
</pre>
<p>Si vous avez suivi cette série d’articles, vous savez que j’aime utiliser <code>alert()</code> pour les exemples de code. Vous savez peut-être aussi qu’<code>alert()</code> est une API assez horrible car elle est synchrone. Dès lors qu’une alerte apparaît à l’écran, les événements liés aux entrées de l’utilisateur ne sont plus déclenchés. Votre code JS (en réalité, toute votre interface utilisateur) est interrompu jusqu’à ce que l’utilisateur clique sur OK.</p>
<p>Bref, avec tout ça, alert() est un mauvais choix pour à peu près tout ce qu’on voudrait faire sur une page web. Je l’utilise car je pense que ces défauts font de alert() un bon outil d’apprentissage.</p>
<p>Je serais près à abandonner tout ça… si je pouvais faire parler un chat.</p>
<pre>
var messages = ["Miaou !", "Je suis un chat qui parle !", "Les callbacks c'est rigolo!"];
for (var i = 0; i < messages.length; i++) {
setTimeout(function () {
chat.miauler(messages[i]);
}, i * 1500);
}
</pre>
<p><a href="https://jsfiddle.net/jmLd8df0/" hreflang="en" title="JSFiddle">Regardez ce code (mal) fonctionner !</a></p>
<p>Il y a quelque chose qui cloche… Au lieu de dire les trois messages dans l’ordre, le chat dit « undefined » à trois reprises.</p>
<p>Voyez-vous la bogue ?
<a href="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/bogue.jpg" title="bogue.jpg"><img src="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/.bogue_m.jpg" alt="bogue.jpg" style="display:block; margin:0 auto;" title="bogue.jpg, août 2015" /></a>
Crédit photo : <a href="https://www.flickr.com/photos/alexander_elzinga/" hreflang="en" title="Alexander Elzinga - Flickr">Elzinga Alexander</a></p>
<p>Le problème est le suivant : il n’y a qu’une seule variable <code>i</code>. Elle est partagée par la boucle et les trois appels du callback pour le timeout. Lorsque la boucle a fini de s’exécuter, <code>i</code> vaut 3 (car <code>messages.length</code> vaut 3) et aucun des callbacks n’a encore été appelé à ce stade.</p>
<p>Aussi, lorsque le premier timeout se déclenche et appelle <code>chat.miauler(messages<a href="https://tech.mozfr.org/post/2015/08/06/i" title="i">i</a>)</code>, c’est <code>messages[3]</code> qui est utilisé. Celui-ci vaut, bien entendu, <code>undefined</code>.</p>
<p>Il existe de nombreuses manières pour corriger ce problème (<a href="https://jsfiddle.net/jqpap5rx/" hreflang="en" title="JSFiddle">en voici une</a>). Ce problème provient avant tout des règles de portées qui s’appliquent à <code>var</code>. En fait, ce qu’on aurait aimé, c’est de ne jamais avoir eu ce problème.</p>
<h2>Vous avez aimé let, vous allez adorer var</h2>
<p>Pour une grande partie, les erreurs de conception de JavaScript ne peuvent pas être réparées (c’est valable également pour les autres langages de programmation mais ça s’applique <em>particulièrement</em> à JavaScript). La rétro-compatibilité implique de ne jamais changer le comportement du code JS qui existe sur le Web. Même le comité de standardisation ne peut pas dire « réglons ces bizarreries liées à l’insertion automatique de points-virgules ». Ceux qui implémentent les navigateurs refuseraient de le faire car cela serait trop punitif pour leurs utilisateurs.</p>
<p>Il y a dix ans, lorsque Brendan Eich décida de régler ce problème, il n’y avait qu’une seul solution envisageable.</p>
<p>Il ajouta un nouveau mot-clé, <code>let</code>, qui pourrait être utilisé pour déclarer les variables, comme <code>var</code>, mais qui utiliserait de meilleures règles pour les portées.</p>
<p>Ce mot-clé ressemble à :</p>
<pre>
let t = readTachymeter();
</pre>
<p>Ou encore :</p>
<pre>
for (let i = 0; i < messages.length; i++) {
...
}
</pre>
<p><code>let</code> et <code>var</code> se comportent différemment donc si vous recherchez et remplacez toutes les occurrences de « var » par « let » dans votre code, cela pourrait casser certains endroits qui dépendent (probablement involontairement) des particularités de <code>var</code>. Toutefois, dans la majorité des cas, dans du code ES6, il est conseillé d’arrêter d’utiliser <code>var</code> et de passer à <code>let</code>. Certains disent ainsi que « <code>let</code> est le nouveau <code>var</code> ».</p>
<p>Quelles sont les différences exactes entre <code>let</code> et <code>var</code> ? Eh bien je suis ravi que vous ayez posé la question !</p>
<ul>
<li><p><strong>Les variables déclarées avec <code>let</code> ont une portée qui s’étend sur le bloc courant.</strong> La portée d’une telle variable correspond simplement au bloc dans lequel on se trouve et pas à la fonction englobante. Les variables sont toujours « remontées » mais plus à tort et à travers. Dans l’exemple précédent avec <code>runTowerExperiment</code>, utiliser <code>let</code> à la place de <code>var</code> suffit à régler le problème. Si vous utilisez <code>let</code> partout, vous n’aurez plus jamais ce genre d’erreur.</p>
</li>
<li><p><strong>Les variables déclarées avec <code>let</code> dans la portée globale ne sont pas des propriétés de l’objet global.</strong> Autrement dit, on ne peut pas y accéder avec <code>window.maVariable</code>. Ces variables « vivent » dans la portée d’un bloc invisible qui englobe tout le code JavaScript qui s’exécute sur la page.</p>
</li>
<li>
<p><strong>Les boucles de la forme <code>for (let x ...)</code> créent une nouvelle liaison pour <code>x</code> à chaque itération.</strong></p>
<p>Cette différence est assez subtile. Cela signifie que si un boucle <code>for (let ...)</code> est exécutée plusieurs fois et que cette boucle contient une fermeture (”closure”) (comme dans notre exemple avec le chat qui parle), chaque fermeture capturera une copie différente de la variable de boucle au lieu que chaque fermeture capture la même variable de boucle.</p>
<p>Ainsi, l’exemple du chat peut lui aussi être corrigé en passant simplement de <code>var</code> à <code>let</code>.</p>
<p>Cela s’applique aux trois boucles <code>for</code> : <code>for-of</code>, <code>for-in</code> et à la boucle <code>for</code> classique héritée de C et parée de ses trois points-virgules.</p></li>
<li><p><strong>Si on utilise un variable <code>let</code> avant qu’elle soit déclarée, cela déclenchera une erreur.</strong> La variable n’est pas ”initialisée” tant que le flux d’instructions n’a pas atteint la ligne de code où la variable est déclarée. Par exemple : </p>
<pre>
function update() {
console.log("heure actuelle :", t); // ReferenceError
...
let t = readTachymeter();
}
</pre>
<p>Cette règle existe pour vous permettre de repérer plus facilement les bugs. Plutôt que d’avoir des résultats qui valent <code>NaN</code>, vous aurez une exception sur la ligne de code ayant causé le problème.</p>
<p>Cette période, pensant laquelle la variable appartient à la portée mais n’est pas initialisée est appelée la « ”zone morte temporaire” ». J’espère qu’avec ce jargon, JavaScript inspirera bientôt des récits de science-fiction. Je n’ai rien lu de tel pour le moment.</p>
<p>(Détails croustillants qui concernent les performances : dans la plupart des cas, vous pouvez déterminer si la déclaration a été faite rien qu’en regardant le code, de cette façon, le moteur JavaScript n’a pas à faire un test supplémentaire à chaque fois qu’on accède à la variable pour savoir si celle-ci a été initialisée. Malgré tout, dans une fermeture, ce n’est pas toujours évident. Dans ces situations, le moteur JavaScript effectuera une vérification lors de l’exécution. Cela signifie que <code>let</code> peut parfois être un soupçon plus lent que <code>var</code>.)</p>
<p>(Détails croustillants sur les portées dans d’autres univers : dans certains langages, la portée de la variable débute à l’endroit où elle est déclarée plutôt que de s’étendre avant sur le début du bloc englobant. Le comité de standardisation a pensé à utiliser ce type de portée pour <code>let</code>. De cette façon, le <code>t</code> qui entraînait une <code>ReferenceError</code> n’aurait simplement pas appartenu à la portée ultérieure de <code>let t</code> et il n’aurait donc pas fait référence à cette variable. Il aurait même pu faire référence à une variable de la portée englobante. Toutefois, cette approche n’a pas bien fonctionné avec les fermetures et les remontées de fonctions, en fin de compte, elle fut abandonnée.</p>
</li>
<li><p><strong>Redéclarer une variable avec <code>let</code> lève une exception <code>SyntaxError</code>.</strong></p>
<p>Cette règle existe également pour vous permettre de détecter les erreurs triviales. C’est cette différence qui risque d’avoir le plus d’impact si vous remplacez tous les <code>var</code> par <code>let</code> car cela s’applique également aux variables globales déclarées avec <code>let</code>.</p>
<p>Si vous avez plusieurs scripts qui déclarent tous la même variable globale, vous feriez mieux de continuer à utiliser <code>var</code> pour ça. Si vous échangez avec <code>let</code>, le script qui sera chargé après le premier échouera avec une erreur.</p>
<p>Sinon, vous pouvez utiliser les modules ES6… mais c’est une autre histoire.</p>
</li>
</ul>
<p>(Détails croustillants sur la syntaxe : <code>let</code> est un mot-clé réservé en mode strict. En mode non-strict, pour garantir la rétrocompatibilité, vous pouvez toujours déclarer des variables, des fonctions et des arguments nommés <code>let</code>. Par exemple, vous pouvez tout à fait écrire : <code>var let = "q";</code> ! En revanche, <code>let let;</code> n’est pas du tout autorisé.)</p>
<p>En dehors de ces différences, <code>let</code> et <code>var</code> sont assez semblables. Tous les deux permettent de déclarer plusieurs variables à la suite en les séparant par des virgules et tous les deux permettent d’utiliser <a href="https://tech.mozfr.org/post/2015/06/05/ES6-en-details-%3A-la-decomposition" hreflang="fr" title="La décomposition ES6 - tech.mozfr.org">la décomposition</a>.</p>
<p>Il est à noter que les déclarations <code>class</code> se comportent comme <code>let</code> et non comme <code>var</code>. Si vous chargez un script qui contient une même classe définie plusieurs fois, vous aurez une erreur à la deuxième déclaration.</p>
<h2>const</h2>
<p>Bien, encore une chose !</p>
<p>ES6 introduit un troisième mot-clé qui peut être utilisé avec <code>let</code> : <code>const</code>.</p>
<p>Les variables déclarées avec <code>const</code> ressemblent à celles déclarées avec <code>let</code> à la seule différence qu’il est impossible d’affecter une valeur à une variable <code>const</code> autrement que lors de sa déclaration. Si on tente d’affecter une valeur à une variable <code>const</code> après sa déclaration, cela provoquera une <code>SyntaxError</code>.</p>
<pre>
const MAX_CAT_SIZE_KG = 3000; //
MAX_CAT_SIZE_KG = 5000; // SyntaxError
MAX_CAT_SIZE_KG++; // bien essayé mais ca lève toujours une SyntaxError
</pre>
<p>Pour faire les choses bien, il est impossible déclarer une constante sans fournir de valeur.</p>
<pre>
const héhé; // SyntaxError, coquinou va
</pre>
<h2>Espace de noms de Zeus Marty !</h2>
<blockquote><p>« Les espaces de noms sont diablement formidables — utilisons-les plus encore ! » Tim Peters, <em>The Zen of Python</em></p></blockquote>
<p>Sous le capôt, les portées imbriquées sont un des concepts fondamentaux sur lesquels les langages de programmation sont construits. Ça fonctionne comme ça depuis <a href="https://fr.wikipedia.org/wiki/Algol_%28langage%29" hreflang="fr" title="Algol - Wikipédia">ALGOL</a>, une bagatelle d’environ 57 ans. Et c’est encore plus vrai aujourd’hui.</p>
<p>Avant ES3, JavaScript n’avait que les portées globales et les portées des fonctions (passons outre les instructions <code>with</code> s’il vous plaît). ES3 a introduit les instructions <code>try-catch</code> ce qui a impliqué l’ajout d’une nouvelle sorte de portée, utilisée uniquement pour les variables d’exceptions dans les blocs <code>catch</code>. ES5 a ajouté une portée qui est utilisée en mode strict avec <code>eval()</code>. ES6 ajoute les portées des blocs, les portées des boucles <code>for</code>, la nouvelle portée pour les variables <code>let</code> globales, les portées des modules et les portées additionnelles utilisées lorsque les valeurs par défaut sont évaluées avec les arguments.</p>
<p>Toutes ces portées supplémentaires ajoutées depuis ES3 sont nécessaires afin que les fonctionnalités procédurales et objets de JavaScript soient aussi souples, précises et intuitives que le sont les fermetures. Ces portées sont aussi là pour que toutes ces fonctionnalités interagissent sans problème avec les fermetures. Peut-être que vous n’aviez jamais remarqué ces règles de portées jusqu’à aujourd’hui. Si c’est le cas, cela signifie que le langage fait bien son travail.</p>
<h2>Puis-je utiliser <code>let</code> et <code>const</code> dès maintenant ?</h2>
<p>Oui. pour les utiliser sur le Web, vous devrez vous servir d’un compilateur ES6 compiler comm <a href="http://babeljs.io/" hreflang="en" title="BabelJS">Babel</a><a href="http://babeljs.io/" hreflang="en" title="BabelJS"></a>, <a href="https://github.com/google/traceur-compiler#what-is-traceur" hreflang="en" title="Traceur - Google">Traceur</a> ou <a href="http://www.typescriptlang.org/" hreflang="en" title="TypeScript">TypeScript</a> (Babel et Traceur ne prennent pas encore en charge la zone morte temporaire).</p>
<p>io.js supporte <code>let</code> et <code>const</code>, mais seulement pour du code écrit en mode strict. Node.js le supporte aussi mais l’option —harmony est nécessaire.</p>
<p>Brendan Eich a mis en œuvre la première version de <code>let</code> dans Firefox <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Nouveaut%C3%A9s_et_historique_de_JavaScript/1.7" hreflang="fr" title="Nouveautés de JavaScript 1.7 - MDN">il y a neuf ans</a>. La fonctionnalité a été complètement repensée pendant le processus de normalisation. Shu-Yu Guo met à niveau notre implémentation pour correspondre à la norme, avec des revues de code par Jeff Walden entre autres.</p>
<p>Eh voilà, nous sommes dans la dernière ligne droite. La fin de notre navigation épique parmi les fonctionnalités ES6 est en vue. Dans deux semaines, nous allons en finir avec ce qui est probablement la fonctionnalité ES6 la plus attendue de toutes. Mais d’abord, la semaine prochaine, nous aurons un billet qui étend notre couverture antérieure d’une nouvelle fonctionnalité qui est tout simplement <code>super</code>. Alors s’il vous plaît rejoignez-nous parce qu’Eric Faust revient pour examiner à fond les sous-classes ES6 en détails.</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les classesurn:md5:a1c91050ed97fe6f28e92dfa22cd076c2015-07-31T18:30:00+02:002015-08-01T09:03:27+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/07/23/ES6-en-details-%3A-les-proxies" hreflang="fr" title="Les proxies - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/07/es6-in-depth-classes/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à Jérémie et Banban pour la traduction et à goofy pour la relecture :) !</em></p>
<hr /> <p><em>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é).</em></p>
<p>Aujourd’hui, retour aux choses simples après quelques billets précédents assez complexes. Pas de façon complètement nouvelle d’écrire du code avec des générateurs, pas d’objets <code>Proxy</code> superpuissants qui viendraient s’accrocher à l’algorithmique interne du langage JavaScript, pas de nouvelles structures de données qui vous éviteraient de réaliser vos propres solutions. À la place, nous allons parler d’une solution syntaxique et idiomatique pour résoudre un vieux problème : la création de constructeurs d’objets en JavaScript.</p>
<h2>Le problème</h2>
<p>Supposons que l’on veuille créer l’exemple le plus emblématique de la conception orientée objet : la classe Cercle. Imaginons que nous sommes en train de créer un Cercle pour une petite bibliothèque Canvas. Entre autres choses, on pourrait avoir envie de savoir comment réaliser les opérations suivantes :</p>
<ul>
<li>Dessiner un Cercle donné dans un Canvas donné.</li>
<li>Se souvenir du nombre de Cercles que l’on a créés jusque-là.</li>
<li>Se souvenir du rayon d’un Cercle en particulier et garantir qu’il ne puisse être modifié.</li>
<li>Calculer la surface d’un Cercle donné.</li>
</ul>
<p>JavaScript tel qu’on le connait actuellement nous indique que l’on doit tout d’abord créer le constructeur de cet objet sous forme d’une fonction. Ensuite, il est nécessaire d’ajouter toutes les propriétés que l’on pourrait vouloir appliquer à notre objet directement sur cette fonction. Enfin, il va falloir remplacer la propriété <code>prototype</code> de ce constructeur par un objet ad hoc. Cet objet prototype contiendra toutes les méthodes nécessaires aux instances de notre classe. Même pour un exemple très simple, une fois que vous en aurez fini, vous allez vous retrouver avec ce genre de code un peu écœurant.</p>
<pre>
function Cercle(rayon) {
this.rayon = rayon;
Cercle.nbrDeCercles++;
}
Cercle.dessiner = function dessiner(cercles, canvas) { /* Code pour dessiner dans le Canvas */ }
Object.defineProperty(Cercle, "nbrDeCercles", {
get: function() {
return !this._count ? 0 : this._count;
},
set: function(val) {
this._count = val;
}
});
Cercle.prototype = {
surface: function surface() {
return Math.pow(this.rayon, 2) * Math.PI;
}
};
Object.defineProperty(Cercle.prototype, "rayon", {
get: function() {
return this._radius;
},
set: function(rayon) {
if (!Number.isInteger(rayon))
throw new Error("Le rayon du cercle doit être un entier.");
this._radius = rayon;
}
});
</pre>
<p>Non seulement le code est lourdingue mais il est égalment assez peu intuitif. Il nécessite d’avoir une excellente compréhension du fonctionnement des fonctions et de la façon dont les propriétés et méthodes sont utilisables par les instances d’objets. Cela vous semble compliqué ? Pas d’inquiétude. Tout l’enjeu de cet article est de vous montrer une methode bien plus simple pour écrire un code équivalent.</p>
<h2>Syntaxe de définition des méthodes</h2>
<p>Pour commencer à nettoyer tout ça, ES6 propose une nouvelle syntaxe afin d’ajouter des propriétés exotiques à un objet. S’il a été assez facile d’ajouter la méthode <code>surface</code> à l’objet <code>Cercle.prototype</code> ci-avant, ça n’a pas été une mince affaire de gérer les getters/setters de la propriété <code>rayon</code>. Dans la mesure où JavaScript est de plus en plus utilisé avec une approche orientée objet, de nombreuses personnes ont eu envie de définir des méthodes plus simples pour ajouter de tels accesseurs et mutateurs. Ce dont nous avions besoin, c’était une façon d’ajouter des « méthodes » à un objet aussi simple que <code>obj.prop = methode</code>, sans la lourdeur de <code>Object.defineProperty</code>. On veut pouvoir simplement:</p>
<ul>
<li>Ajouter une fonction ordinaire comme propriété d’un objet</li>
<li>Ajouter une fonction générateur comme propriété d’un objet</li>
<li>Ajouter une fonction accesseur ou mutateur comme propriété d’un objet</li>
<li>Ajouter n’importe laquelle des fonctions ci-avant comme si l’on avait utilisé la syntaxe à crochets [] sur l’objet fini, ce que l’on appelle les « noms de propriétés générés ».</li>
</ul>
<p>Certaines de ces actions étaient impossibles jusqu’à présent. Par exemple, il n’y avait aucun moyen de définir un accesseur ou un mutateur via une affectation directe à <code>obj.prop</code>. Il a donc fallu rajouter une nouvelle syntaxe. Désormais, vous pouvez écrire le code suivant :</p>
<pre>
var obj = {
// Les méthodes sont désormais ajoutées sans le mot clé "function",
// le nom de la propriété devenant le nom de la fonction.
methode(args) { ... },
// Pour créer une méthode qui soit un générateur, ajoutez juste un '*', comme d'habitude.
*genMethode(args) { ... },
// Les accesseurs et mutateurs peuvent désormais être créés directement sur place
// avec l'aide de |get| et |set|. Cependant ils ne peuvent pas être des générateurs.
// Notez qu'un accesseur défini de cette façon ne doit avoir aucun argument.
get propName() { ... },
// Notez qu'un mutateur défini de cette façon doit avoir exactement un argument.
set propName(arg) { ... },
// Pour pouvoir gérer le quatrième cas ci-avant, la syntaxe à crochets [] est autorisée
// partout où un nom de fonction est attendu. Cela permet d'utiliser des symboles,
// des appels de fonction, des concaténations de chaînes et toute autre méthode pouvant
// être évaluée comme un identifiant de propriété valide. L'exemple ci-après crée une
// méthode mais cela fonctionne également pour les accesseurs, mutateurs et générateurs.
[functionQuiRenvoieUnNomDePropriété()] (args) { ... }
};
</pre>
<p>En utilisant cette nouvelle syntaxe, on peut reécrire notre exemple de la manière suivante :</p>
<pre>
function Cercle(rayon) {
this.rayon = rayon;
Cercle.nbrDeCercles++;
}
Cercle.dessiner = function dessiner(cercles, canvas) {
/* Code pour dessiner dans le Canvas */
}
Object.defineProperty(Cercle, "nbrDeCercles", {
get: function() {
return !this._count ? 0 : this._count;
},
set: function(val) {
this._count = val;
}
});
Cercle.prototype = {
area() {
return Math.pow(this.rayon, 2) * Math.PI;
},
get rayon() {
return this._radius;
},
set rayon(radius) {
if (!Number.isInteger(radius))
throw new Error("Le rayon du cercle doit être un entier.");
this._radius = radius;
}
};
</pre>
<p>Si vous voulez pinailler, ce code n’est pas tout à fait identique au précédent. Les méthodes définies via la notation littérale sont configurables et énumérables, alors que les accesseurs et mutateurs définis précédemment ne sont ni configurables ni énumérables. Dans les faits, c’est quelque chose que l’on remarque rarement et j’ai décidé d’éluder cette question pour rester simple.</p>
<p>Quoi qu’il en soit, c’est déjà beaucoup mieux, n’est-ce pas ? Malheureusement, même armé de cette nouvelle syntaxe, on ne peut pas faire grand-chose pour la définition de <code>Cercle</code> puisque l’on doit toujours définir une fonction. Or, il n’est pas possible de définir les propriétés de cette fonction pendant qu’on définit la fonction elle-même.</p>
<h2>Syntaxe de définition des classes</h2>
<p>Bien que se soit mieux, ça ne satisfait toujours pas les personnes qui veulent un solution plus simple pour la conception orientée objet en JavaScript. Leur argument est le suivant : les autres langages possèdent une structure faite pour gérer la conception orientée objet : les classes</p>
<p>Ok. Ajoutons-donc les classes.</p>
<p>Ce que l’on veut, c’est un mécanisme qui nous permettra d’ajouter des méthodes à un constructeur identifié et d’ajouter des méthodes à son prototype, méthodes qui seront donc accessibles aux instances de la classe. Vu qu’on a déjà notre nouvelle syntaxe de définition des méthodes, autant l’utiliser. On a juste besoin d’un moyen de différencier les méthodes génériques, utilisables pour toutes les instances de classe, et les méthodes spécifiques à chaque instance. En C++ ou en Java, le mot-clé pour faire cette différence c’est <code>static</code>. Il en vaut bien un autre, utilisons celui-ci.</p>
<p>À présent, il serait bien utile d’avoir un moyen pour identifier la méthode qui, parmi toutes les autres, sera le constructeur de la classe. En C++ ou en Java, cette fonction a le même nom que la classe sans type de retour. Puisque JavaScript n’a, de toute façon, pas de type de retour et que l’on a besoin d’une propriété <code>constructor</code> pour des questions de rétro-compatibilité, on va appeler cette méthode <code>constructor</code>.</p>
<p>En faisant tout ça, on peut réécrire notre classe Cercle comme elle aurait toujours dû l’être :</p>
<pre>
class Cercle {
constructor(rayon) {
this.rayon = rayon;
Cercle.nbrDeCercles++;
};
static dessiner(cercle, canvas) {
// Code pour dessiner dans le Canvas
};
static get nbrDeCercles() {
return !this._count ? 0 : this._count;
};
static set nbrDeCercles(val) {
this._count = val;
};
surface() {
return Math.pow(this.rayon, 2) * Math.PI;
};
get rayon() {
return this._radius;
};
set rayon(radius) {
if (!Number.isInteger(radius))
throw new Error("Le rayon du cercle doit être un entier.");
this._radius = radius;
};
}
</pre>
<p>Wow ! Non seulement, nous avons pu regrouper tout ce qui est propre à notre Cercle mais en plus c’est si… simple. C’est clairement mieux que ce que nous avions au départ. Malgré tout, vous allez sans doute avoir des questions ou bien vous allez trouver certains cas particuliers. J’ai fait de mon mieux pour anticiper et répondre à certains d’entre eux :</p>
<ul>
<li><strong>Pourquoi des point-virgules ?</strong> — Dans une tentative de faire en sorte que ça ressemble à des classes « traditionnelles », nous avons choisi d’utiliser un séparateur classique. Vous n’aimez pas ça ? C’est optionnel, les delimiteurs ne sont pas obligatoires.</li>
<li><strong>Comment faire si je ne veux pas de constructeur mais que je veux quand même ajouter des méthodes à une classe ?</strong> — Pas de problème. La méthode <code>constructor</code> est complètement facultative. Si vous n’en définissez aucune, ça va se comporter comme si vous aviez écrit <code>constructor() {}</code>.</li>
<li><strong>Un constructeur peut-il être un générateur ?</strong> — Nope ! Ajouter un constructeur qui n’est pas une fonction normale engendrera une erreur <code>TypeError</code>. Ça vaut pour les générateurs mais également pour les accesseurs et mutateurs.</li>
<li><strong>Puis-je définir un constructeur via un nom de propriété généré ?</strong> — Hélas non. C’est vraiment difficile a gérer, on n’a donc même pas essayé. Si vous définissez une méthode avec un nom de propriété généré qui se trouve être <code>constructor</code>, vous obtiendrez bien une methode appelé <code>constructor</code> mais ce ne sera pas le constructeur de la classe.</li>
<li><strong>Que se passe-t-il si je change la valeur de Cercle ? Cela posera-t-il des problèmes si j’utilise <code>new Cercle</code> ?</strong> Non ! Comme pour les expressions de fonctions, les classes reçoivent une structure interne pour un nom donné. Cette structure ne peut pas être modifiée depuis l’extérieur, quelle que soit la valeur utilisée pour modifier la variable <code>Cercle</code> dans la portée courante. <code>Cercle.nbrDeCercles++</code> du constructeur continuera de fonctionner normalement.</li>
<li><strong>Certes, mais je pourrais passer un littéral objet directement comme argument d’une fonction. Ces nouvelles « classes » ne fonctionneraient plus, non ? </strong> – Heureusement, ES6 apporte également les expressions de classe. Celles-ci peuvent être nommées ou anonymes et elles se comporteront exactement comme ce qu’on a vu avant sauf qu’elles ne créeront pas de variable dans la portée de la déclaration.</li>
<li><strong>Et au fait, qu’en est-il de l’énumérabilité et du reste ?</strong> – Les gens souhaitaient pouvoir installer des méthodes sur des objets mais n’obtenir que les propriétés de données lors d’une énumération, ce qui est logique. Pour cette raison, les méthodes ajoutées aux classes sont configurables mais pas énumérables.</li>
<li><strong>Euh, attendez ? Où sont mes variables d’instances et mes constantes statiques ?</strong> – Bien vu. À l’heure actuelle, elles n’existent pas avec les classes ES6. Mais c’est bien parti pour les avoir par la suite : le sujet a déjà été abordé lors des réunions de spécifications par moi et plusieurs personnes favorables à l’idée d’avoir à la fois des valeurs statiques et des constantes utilisables avec cette syntaxe de classe. D’autres discussions sont à venir sur ce sujet.</li>
<li><strong>OK, ça a l’air super ! Puis-je utiliser cette fonctionnalité ?</strong> – Pas exactement. Certaines prothèses existent (notamment grâce à Babel) et vous pouvez actuellement vous amuser avec les classes. Malheureusement, cela va prendre encore un peu de temps avant qu’elles ne soient implémentées au sein des principaux navigateurs. Tout ce dont nous avons parlé aujourd’hui a été implémenté par votre serviteur et est disponible dans la version Nightly de Firefox. Les classes sont implémentées dans Edge et Chrome mais ne sont pas activées par défaut. Il semblerait qu’à l’heure actuelle, il n’y ait pas d’implémentation pour Safari.</li>
<li><strong>Java et C++ permettent de créer des sous-classes et utilisent le mot-clé <code>super</code>. Cet article n’en parle pas, est-ce que JavaScript permet de faire pareil ?</strong> Oui, toutefois c’est un sujet suffisamment vaste pour un autre billet. Nous reviendrons prochainement pour parler des sous-classes et explorer le pouvoir des classes JavaScript.</li>
</ul>
<p>Je n’aurais pas été capable d’implémenter les classes sans l’aide apportée par Jason Orendorff et Jeff Walden et la relecture de code qu’ils ont effectuée.</p>
<p>La semaine prochaine, Jason Orendorff reviendra pour expliquer en détails les nouvelles instructions ES6 <code>let</code> et <code>const</code>.</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les proxiesurn:md5:52930032a04b8586adfd0c9e61b785af2015-07-24T17:34:00+02:002015-07-29T20:14:50+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/07/14/ES6-en-details-%3A-les-generateurs--la-suite" hreflang="fr" title="Les générateurs, la suite - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/07/es6-in-depth-proxies-and-reflect/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à Ilphrin et Banban pour la traduction et la relecture !</em></p>
<hr /> <p><em><a href="https://tech.mozfr.org/tag/ES6%20en%20d%C3%A9tails">ES6 en détails</a> 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é).</em></p>
<p>Voilà ce qu’on va s’amuser à construire aujourd’hui :</p>
<pre>
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`accès à ${key} !`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`modification de ${key} !`);
return Reflect.set(target, key, value, receiver);
}
});
</pre>
<p>Pour un premier exemple, c’est un peu ardu. Je vais expliquer les différentes parties au fur et à mesure. Pour le moment, regardons l’objet que nous venons de créer :</p>
<pre>
> obj.count = 1;
accès à count !
> ++obj.count;
accès à count !
modification de count !
2
</pre>
<p>Qu’est-ce qui se passe ? Nous sommes en train d’intercepter l’accès aux propriétés pour cet objet. Nous surchargeons l’opérateur <code>.</code>.</p>
<h2>Comment ça marche</h2>
<p>La virtualisation est un des meilleurs tours de passe-passe en informatique. C’est une technique générique qui permet de faire des choses extraordinaires. Voici comment elle fonctionne :</p>
<ol>
<li>Prenez une photo<a href="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/power-plant.jpg" title="power-plant.jpg"><img src="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/.power-plant_m.jpg" alt="power-plant.jpg" style="display:block; margin:0 auto;" title="power-plant.jpg, juil. 2015" /></a><a href="https://www.flickr.com/photos/martini_dk/369891979" hreflang="en" title="Flickr - martini_dk">Crédit photo : Martin Nikolaj Bech</a></li>
<li>Dessinez une forme quelque part sur cette image<a href="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/power-plant-with-outline.png" title="power-plant-with-outline.png"><img src="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/.power-plant-with-outline_m.png" alt="power-plant-with-outline.png" style="display:block; margin:0 auto;" title="power-plant-with-outline.png, juil. 2015" /></a></li>
<li>Maintenant, remplacez tout ce qu’il y a à l’intérieur (ou à l’extérieur) de cette forme avec quelque chose d’inattendu. Une seule règle à respecter : la règle de la compatibilité ascendante. Le remplacement effectué doit ressembler suffisamment à ce qu’il y avait avant pour qu’un observateur de l’autre coté de la ligne ne puisse pas remarquer que quelque chose a changé.<a href="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/wind-farm.png" title="wind-farm.png"><img src="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/.wind-farm_m.png" alt="wind-farm.png" style="display:block; margin:0 auto;" title="wind-farm.png, juil. 2015" /></a><a href="https://www.flickr.com/photos/bevgoodwin/8671334130/" hreflang="en" title="Flickr - bevgoodwin">Crédit photo: Beverley Goodwin.</a></li>
</ol>
<p>Ce tour de passe-passe est également présenté dans des films comme <em>The Truman Show</em> et <em>Matrix</em> où une personne est à l’intérieur de cette forme et où le reste du monde a été remplacé par une illusion de normalité.</p>
<p>Pour respecter cette règle de la compatibilité ascendante, il faut que le contenu utilisé pour le remplacement soit soigneusement conçu. Cependant, le plus important dans ce tour de force est de dessiner la bonne forme.</p>
<p>Par « forme », j’entends ici la frontière d’une API. Une interface. Les interfaces définissent comment deux morceaux de code peuvent interagir entre eux et ce que chacun attend de l’autre. Si une interface est déjà conçue dans le système, la forme est déjà dessinée. Vous savez que vous pouvez remplacer un des deux côtés sans que l’autre soit impacté.</p>
<p>C’est quand il n’y a <em>pas</em> d’interface existante que vous devrez faire preuve de créativité. Certains des projets logiciels les plus géniaux ont vu le jour grâce à la création d’une frontière d’API qui n’existait pas auparavant. Créer cette interface de toute pièce constitue une prouesse d’ingénierie.</p>
<p><a href="https://en.wikipedia.org/wiki/Virtual_memory" hreflang="en" title="Virtual memory - Wikipedia">La mémoire virtuelle</a>, <a href="https://fr.wikipedia.org/wiki/Virtualisation" hreflang="fr" title="Virtualisation - Wikipédia">la virtualisation des composants matériels</a>, <a href="https://fr.wikipedia.org/wiki/Docker_%28logiciel%29" hreflang="fr" title="Docker - Wikipédia">Docker</a>, <a href="http://valgrind.org/" hreflang="en" title="Valgrind">Valgrind</a>, <a href="http://rr-project.org/" hreflang="en" title="rr">rr</a> sont tous, sous certains aspects, des projets qui ont impliqué la création de nouvelles interfaces, parfois inattendues, au sein de systèmes existants. Dans certains cas, cela a pris des années, a nécessité de nouvelles fonctionnalités des systèmes d’exploitation voire de nouveaux composants matériels pour que la nouvelle frontière fonctionne correctement.</p>
<p>Les meilleures techniques de virtualisation apportent également une nouvelle compréhension de ce qui est virtualisé. Avant d’écrire une API pour interfacer quelque chose, il faut le comprendre. Une fois que vous l’avez compris, vous pouvez réaliser des prouesses.</p>
<p>ES6 introduit le support de la virtualisation pour le concept primordial de JavaScript : l’objet.</p>
<h2>Qu’est-ce qu’un objet ?</h2>
<p>Sérieusement, réfléchissez-y pendant quelques minutes avant de poursuivre. Continuez cet article lorsque vous avez construit votre définition de ce qu’est un objet.
<a href="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/thinker.jpg" title="thinker.jpg"><img src="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/.thinker_m.jpg" alt="thinker.jpg" style="display:block; margin:0 auto;" title="thinker.jpg, juil. 2015" /></a><a href="https://www.flickr.com/photos/mustangjoe/5966894496/" hreflang="en" title="Flickr - mustangjoe">Crédit photo : Joe deSousa.</a></p>
<p>Cette question est trop difficile ! Je n’ai jamais entendu de définition complètement satisfaisante.</p>
<p>Est-ce bien surprenant ? La définition de concepts fondamentaux est toujours un exercice périlleux — prenez certaines des premières définitions des <a href="http://aleph0.clarku.edu/~djoyce/java/elements/bookI/bookI.html"><em>Éléments</em> d’Euclide</a> par exemple. La spécification du langage ECMAScript n’a donc pas à rougir quand elle définit un objet comme « un membre du type Object » (ce qui nous aide beaucoup).</p>
<p>Plus loin, la spécification ajoute « Un objet est une collection de propriétés ». Pas mal. Si vous voulez une définition, celle-ci pourra faire l’affaire pour le moment. Nous y reviendrons plus tard.</p>
<p>Plus haut, j’ai dit que pour écrire une API afin d’interfacer quelque chose, il faut le comprendre. D’une certaine façon, je vous ai promis que nous suivrons ce chemin, que nous comprendrons mieux ce que sont les objets et que nous ferons des choses formidables.</p>
<p>Prenons donc le chemin emprunté par le comité de standardisation ECMAScript et voyons ce qu’il faudrait pour définir une API, une interface, pour les objets JavaScript. De quels types de méthodes avons-nous besoin ? Que peuvent faire les objets ?</p>
<p>D’une certaine façon, cela dépend de l’objet. Les objets <code>Element</code> du DOM peuvent réaliser certaines choses, les objets <code>AudioNode</code> peuvent faire d’autres choses… Toutefois, il y a quelques capacités partagées par tous les objets :</p>
<ul>
<li>Les objets ont des propriétés. Vous pouvez accéder à ces propriétés, les initialiser et les modifier, les supprimer et ainsi de suite.</li>
<li>Les objets ont des prototypes. C’est grâce à eux que l’héritage fonctionne en JavaScript.</li>
<li>Certains objets sont des fonctions ou des constructeurs. Vous pouvez les appeler.</li>
</ul>
<p>Tout ce que les programmes JavaScript font avec les objets (ou presque) est fait en utilisant les propriétés, les prototypes et les fonctions. Même le comportement spécial des objets <code>Element</code> ou <code>AudioNode</code> provient des méthodes appelées qui sont des propriétés fonctionnelles héritées.</p>
<p>C’est pour cette raison que lorsque le comité de standardisation a défini un ensemble de 14 méthodes internes, l’interface commune à tous les objets, celles-ci se concentraient sur ces trois aspects fondamentaux.</p>
<p>La liste complète est décrite dans <a href="http://www.ecma-international.org/ecma-262/6.0/index.html#table-5" title="ECMAScript 6">les tableaux 5 et 6 du standard ES6</a>. Ici, je n’en décrirai que quelques-unes. Les doubles crochets, <code>[[ ]]</code>, un peu étranges indiquent qu’il s’agit de méthodes internes, inaccessibles via du code JavaScript ordinaire. Ces méthodes ne peuvent pas être appelées, supprimées ou surchargées.</p>
<ul>
<li><strong><code>obj.[[Get]](key, receiver)</code></strong> – Obtenir la valeur d’une propriété.
<p>Appelée lorsque le code JS exécute : <code>obj.prop</code> ou <code>obj[key]</code>.</p>
<p><code>obj</code> est l’objet dans lequel nous cherchons à accéder à une propriété, <code>receiver</code> est l’objet sur lequel nous commençons à chercher cette propriété. Parfois, nous voulons rechercher dans plusieurs objets. <code>obj</code> peut être un objet sur la chaîne de prototypes de <code>receiver</code>.</p></li>
<li><strong><code>obj.[[Set]](key, value, receiver)</code></strong> – Affecter une propriété à un objet.
<p>Appelée lorsque le code JS exécute : <code>obj.prop = valeur</code> ou <code>obj[key] = valeur</code>.</p>
<p>Lors d’une affectation comme <code>obj.prop += 2</code>, la méthode <code>[[Get]]</code> est appelée en premier et ensuite la méthode <code>[[Set]]</code>. De même pour <code>++</code> et <code>--</code>.</p>
</li><li><strong><code>obj.[[HasProperty]](key)</code></strong> – Teste l’existence d’une propriété.
<p>Appelée lorsque le code JS exécute : <code>key in obj</code>.</p>
</li><li><strong><code>obj.[[Enumerate]]()</code></strong> – Liste les propriétés énumérables de l’objet.
<p>Appelée lorsque le code JS exécute : <code>for (key in obj) …</code></p>
<p>Ceci retourne un objet itérateur, et c’est ainsi qu’une boucle <code>for-in</code> obtient les noms des propriétés d’un objet.</p>
</li><li><strong><code>obj.[[GetPrototypeOf]]()</code></strong> – Retourne le prototype de l’objet.
<p>Appelée lorsque le code JS exécute : <code>obj.__proto__</code> ou <code>Object.getPrototypeOf(obj)</code>.</p>
</li><li><strong><code>functionObj.[[Call]](thisValue, arguments)</code></strong> – Appelle une fonction.
<p>Optionnelle : tous les objets ne sont pas des fonctions.</p>
<p>Appelé lorsque le code JS exécute : <code>fonctionObj()</code> ou <code>x.methode()</code>.</p>
</li><li><strong><code>constructorObj.[[Construct]](arguments, newTarget)</code></strong> – Invoque un constructeur.
<p>Appelée lorsque le code JS exécute : <code>new Date(2890, 6 2)</code>, par exemple.</p>
<p>Optionelle : tous les objets n’ont pas de constructeur.</p>
<p>L’argument <code>newTarget</code> joue un rôle pour les sous-classes. Nous l’aborderons dans un prochain billet.</p>
</li>
</ul>
<p>Vous pouvez peut-être deviner certaines des sept autres méthodes.</p>
<p>Avec le standard ES6, à chaque fois que c’est possible, le moindre petit morceau de syntaxe ou de fonction intégrée qui manipule des objets est spécifié selon l’une des 14 méthodes internes. ES6 marque une limite claire autour des mécanismes d’un objet.. Les proxies vous permettent de remplacer ces mécanismqes standard avec du code JavaScript arbitraire.</p>
<p>Dans un moment, lorsque nous commencerons à parler de surcharger ces méthodes internes, souvenez-vous, nous parlerons de surcharger le comportement de la syntaxe comme <code>obj.prop</code>, des fonctions natives comme <code><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/keys" hreflang="fr" title="Object.keys() - MDN">Object.keys()</a></code>, etc.</p>
<h2><code>Proxy</code></h2>
<p>ES6 définit un nouveau constructeur global : <code><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Proxy" hreflang="fr" title="Proxy - MDN">Proxy</a></code>. Il prend deux arguments, un objet cible (<em>target</em>) et un objet gestionnaire (<em>handler</em>). Un exemple très simple pourrait être :</p>
<pre>
var target = {}, handler = {};
var proxy = new Proxy(target, handler);
</pre>
<p>Pour le moment, laissons l’objet gestionnaire de côté et concentrons-nous sur les liens entre le proxy et la cible.</p>
<p>Le comportement du proxy peut être expliqué en une phrase : toutes les méthodes internes du proxy sont retransmises à la cible. Par exemple, si quelque chose appelle <code>proxy.[[Enumerate]]()</code>, cela renverra juste <code>target.[[Enumerate]]()</code>.</p>
<p>Essayons. Utilisons une instruction qui entraîne l’appel de <code>proxy.[[Set]]()</code></p>
<pre>
proxy.couleur = "rose";
</pre>
<p>OK, que s’est-il passé ? <code>proxy.[[Set]]()</code> devrait avoir appelé <code>target.[[Set]]()</code> et cela devrait avoir créé un nouvelle propriété sur <code>target</code>. Est-ce bien le cas ?</p>
<pre>
> target.couleur
"rose"
</pre>
<p>Ça a bien fonctionné. Il en va de même pour les autres méthodes internes. Le proxy, dans la plupart des cas, se comportera exactement comme la cible.</p>
<p>Il y a certaines limites à cette illusion. Vous aurez notamment <code>proxy!==target</code>. Un proxy pourra également recaler certaines opérations à la suite des vérifications de types alors que celles-ci auraient fonctionné pour la cible. Par exemple, si la cible d’un proxy est un <code>Element</code> du DOM, le proxy ne sera pas vraiment un <code>Element</code>. Une opération comme <code>document.body.appendChild(proxy)</code> échouera avec une exception <code>TypeError</code>.</p>
<h2>Les gestionnaires de proxies</h2>
<p>Revenons vers l’objet gestionnaire (<em>handler</em>). C’est cet objet qui rend les proxies utiles.</p>
<p>Les méthodes de l’objet gestionnaire peuvent surcharger n’importe quelle méthode interne du proxy.</p>
<p>Par exemple, si vous voulez intercepter toutes les tentatives de modification/affectation de propriétés pour un objet, il vous suffit de définir une méthode <code><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Proxy/handler/set" hreflang="fr" title="handler.set() - MDN">handler.set()</a></code> :</p>
<pre>
var target = {};
var handler = {
set: function (target, key, value, receiver) {
throw new Error("Merci de ne pas modifier de propriétés sur cet objet.");
}
};
var proxy = new Proxy(target, handler);
> proxy.nom = "angelina";
Error: Merci de ne pas modifier de propriétés sur cet objet.
</pre>
<p>La liste complète des méthodes pour les gestionnaires est documentée sur<a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Proxy#M.C3.A9thodes_pour_le_gestionnaire" hreflang="fr" title="Proxy - MDN"> la page MDN de Proxy</a>. Il y a 14 méthodes, chacune de ces méthodes faisant écho aux 14 méthodes internes définies dans ES6.</p>
<p>Toutes les méthodes pour les gestionnaires sont optionnelles. Si une méthode interne n’est pas interceptée par le gestionnaire, elle est simplement transmise à la cible comme nous l’avons vu auparavant.</p>
<h2>Exemple : objets auto-généres</h2>
<p>Nous en savons désormais assez sur les proxies pour les utiliser à des fins étranges et réaliser quelque chose qui est impossible sans les proxies.</p>
<p>Voici notre premier exercice : créer une fonction <code>Arbre()</code> qui peut faire ceci :</p>
<pre>
> var arbre = Arbre();
> arbre
{ }
> arbre.branche1.branche2.brindille = "vert";
> arbre
{ branche1: { branche2: { brindille: "vert" } } }
> arbre.branche1.branche3.brindille = "jaune";
{ branche1: { branche2: { brindille: "vert" },
branche3: { brindille: "jaune" }}}
</pre>
<p>Observez ici comment tous les objets intermédiaires <code>branche1</code>, <code>branche2</code> et <code>branche3</code> sont créés automatiquement, presque de façon magique, quand ils sont nécessaires. Plutôt pratique n’est-ce pas ? Comment cela pourrait-il fonctionner ?</p>
<p>Jusqu’à maintenant, il n’existait aucun moyen pour que cela puisse fonctionner. Cependant, avec les proxies, il suffit de quelques lignes de code. Il suffit de raccorder <code>arbre.[[Get]]()</code>. Si vous voulez un défi, vous pouvez essayer d’implémenter ceci avant de continuer votre lecture.
<a href="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/maple-tap.jpg" title="maple-tap.jpg"><img src="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/.maple-tap_m.jpg" alt="maple-tap.jpg" style="display:block; margin:0 auto;" title="maple-tap.jpg, juil. 2015" /></a>Pas la meilleure façon de se raccorder sur un arbre en JS. <a href="https://www.flickr.com/photos/chiotsrun/5446345665/" hreflang="en" title="Flick - chiotsrun">Crédit photo : Chiot’s Run.</a></p>
<p>Voici ma solution :</p>
<pre>
function Arbre() {
return new Proxy({}, handler);
}
var handler = {
get: function (target, key, receiver) {
if (!(key in target)) {
target[key] = Arbre(); // créer un sous arbre automatiquement
}
return Reflect.get(target, key, receiver);
}
};
</pre>
<p>Notez l’appel à <code>Reflect.get()</code> à la fin de l’exemple. Il s’agit d’un besoin extrêmement commun, lorsqu’on utilise les méthodes d’un gestionnaire de proxy, que de vouloir dire « maintenant, je souhaite simplement appliquer le comportement par défaut à l’objet cible ». Pour cela, ES6 définit un nouvel objet <code>Reflect</code> qui possède 14 méthodes que l’on peut utiliser à cet effet.</p>
<h2>Exemple : une vue en lecture seule</h2>
<p>Je pense que j’ai pu donner une fausse impression en indiquant que les proxies seraient simples à utiliser. Prenons un exemple supplémentaire pour voir si c’est bien le cas.</p>
<p>Cette fois, le devoir est plus complexe : nous devons implémenter une fonction <code>readOnlyView(object)</code>, qui prend n’importe quel objet et qui renvoie un proxy qui se comporte exactement comme cet objet sauf qu’il est impossible de le modifier. Elle se comporterait de la façon suivante :</p>
<pre>
> var newMath = readOnlyView(Math);
> newMath.min(54, 40);
40
> newMath.max = Math.min;
Error: impossible de modifier, vue en lecture seule
> delete newMath.sin;
Error: impossible de modifier, vue en lecture seule
</pre>
<p>Comment implémenter cette fonction ?
La première étape est d’intercepter toutes les méthodes internes qui pourraient modifier l’objet cible si nous les laissions passer. Il y en a cinq.</p>
<pre>
function NOPE() {
throw new Error("impossible de modifier, vue en lecture seule");
}
var handler = {
// On surcharge les cinq méthodes qui peuvent modifier.
set: NOPE,
defineProperty: NOPE,
deleteProperty: NOPE,
preventExtensions: NOPE,
setPrototypeOf: NOPE
};
function readOnlyView(target) {
return new Proxy(target, handler);
}
</pre>
<p>Y a-t-il des failles à ce système ?</p>
<p>Le plus problème est que la méthode <code>[[Get]]</code>, ainsi que d’autres, peuvent toujours renvoyer des objets modifiables. Par exemple si un objet <code>x</code> est une vue en lecture seule, <code>x.prop</code> pourrait être modifié. C’est une faille digne de ce nom.</p>
<p>Pour corriger cela, nous devons ajouter une méthode <code>handler.get()</code> :</p>
<pre>
var handler = {
...
// On enveloppe les autres résultats dans des vues en lecture seule.
get: function (target, key, receiver) {
// On applique le comportement par défaut.
var result = Reflect.get(target, key, receiver);
// On s'assure de ne pas renvoyer d'objet modifiable !
if (Object(result) === result) {
// result est un object.
return readOnlyView(result);
}
// result est une valeur primitive, déjà non modifiable.
return result;
},
...
};
</pre>
<p>Ce n’est pas suffisant non plus. Il faudra un code similaire pour les autres méthodes, dont <code>getPrototypeOf</code> et <code>getOwnPropertyDescriptor</code>.</p>
<p>D’autres problèmes se posent ensuite. Lorsqu’un accesseur (<em>getter</em>) ou une méthode est appelé via ce type de proxy, la valeur passée à l’accesseur ou à la méthode sera généralement le proxy lui-même. Or, comme nous l’avons vu auparavant, de nombreux accesseurs et méthodes vérifient le type, ce qui empêchera le proxy de passer. Dans ces cas-là, il serait plus pratique de substituer le proxy par l’objet cible. Pouvez-vous trouver comment faire ?</p>
<p>La leçon à tirer de ces exemples : c’est simple de créer un proxy mais difficile de créer un proxy dont le comportement est intuitif.</p>
<h2>Informations diverses et variées</h2>
<ul>
<li><strong>À quoi les proxies sont-ils vraiment utiles ?</strong>
<p>Il sont certainement utiles lorsqu’on souhaite observer ou enregistrer les accès à un objet. Ils seront pratiques pour le débogage. Les frameworks de test pourraient les utiliser pour simuler les vrais objets.</p>
<p>Les proxies sont utiles si vous avez besoin d’un comportement qui n’est pas à la portée d’un objet ordinaire : peupler des propriétés de façon automatique est un exemple.<p>
<p>J’ai horreur de dire ça mais l’une des meilleure façon de voir ce qui se passe dans du code qui utilise des proxies… c’est d’envelopper le gestionnaire de proxy dans un autre proxy qui affiche des informations dans la console à chaque fois qu’une méthode du gestionnaire est appelée.</p>
<p>Les proxies peuvent être utilisés pour restreindre l’accès à un objet comme on l’a vu avec <code>readOnlyView</code>. C’est un cas d’utilisation assez rare pour des applications mais Firefox utilise les proxies en interne afin d’implémenter la sécurité des frontières entre les différents domaines. Les proxies sont un élément clé de <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Gecko/Script_security">notre modèle de sécurité</a>.<p>
</li>
<li><strong>Proxies ♥ WeakMaps.</strong> Dans notre exemple sur <code>readOnlyView</code>, nous avons créé un nouveau proxy à chaque fois que nous accédions à un objet. Il serait possible d’économiser beaucoup de mémoire en mettant les différents proxies créés en cache dans une <code>WeakMap</code>. De cette façon, peu importe le nombre de fois qu’un objet est passé à <code>readOnlyView</code>, seul un proxy sera créé pour celui-ci.
<p>Vu sous cet angle, les proxies sont l’une des raisons d’utiliser <code>WeakMap</code>.</p>
</li>
<li><strong>Les proxies révocables.</strong> ES6 définit également une autre fonction : <code>Proxy.revocable(target.handler)</code> qui crée un proxy de la même façon que new <code>Proxy(target, handler)</code>, sauf que ce proxy peut être révoqué plus tard (<code>Proxy.revocable</code> renvoie un objet avec une propriété <code>.proxy</code> et une méthode <code>.revoke</code>). Une fois qu’un proxy est révoqué, il ne fonctionne plus, toutes ses méthodes internes lèveront des exceptions.
</li>
<li><strong>Les invariants d’objets.</strong> Dans certaines situations, ES6 requiert des résultats renvoyés par les méthodes du gestionnaire de proxy qui soient cohérents avec l’état de l’objet cible. Cette condition existe afin d’appliquer les règles qui concernent l’immuabilité parmi les différents objets, y compris les proxies. Par exemple, un proxy ne peut pas prétendre être inextensible si l’objet cible n’est pas réellement inextensible.
<p>Les règles exactes sont trop complexes pour être vues ici, mais si vous obtenez un message d’erreur qui ressemble à “proxy can’t report a non-existent property as non-configurable” (“le proxy ne peut pas signaler qu’une propriété inexistante n’est pas configurable”), ce sera pour cette raison. Le remède le plus probable est de modifier ce que le proxy prétend sur lui-même. Une autre possibilité est de modifier l’objet cible au vol afin que celui-ci reflète l’état du proxy.</p>
</li>
</ul>
<h2>Alors finalement, qu’est-ce qu’un objet ?</h2>
<p>Je pense que nous en étions resté à « Un objet est une collection de propriétés ».</p>
<p>Je ne suis pas entièrement satisfait de cette définition, même en considérant comme acquis qu’on les intègre aux prototypes et aux appels. Je pense que le mot « collection » est exagéré vu comment un proxy ressemble à une collection. Ses méthodes de manipulation pourraient faire n’importe quoi. Elles pourraient retourner un résultat aléatoire.</p>
<p>En déterminant ce qu’un objet peut faire, en standardisant ces méthodes et en ajoutant la virtualisation en fonction prioritaire que tout le monde peut utiliser, le standard ECMAScript a ouvert le champ des possibles.</p>
<p>Les objets peuvent désormais être presque n’importe quoi.</p>
<p>Peut-être que la réponse la plus honnête à la question « Qu’est-ce qu’un objet ? » est maintenant de reprendre les 12 méthodes internes comme définition. Un objet est quelque chose dans un programme JS qui a une opération <code>[[Get]]</code>, un opération <code>[[Set]]</code> et ainsi de suite.</p>
<p>Est-ce que nous comprenons mieux les objets avec tout ça ? Je n’en suis pas si sûr ! Avons-nous fait des choses étonnantes ? Certainement. Nous avons fait des choses qui auraient été auparavant impossibles en JS.</p>
<h2>Puis-je utiliser les proxies dès maintenant ?</h2>
<p>Non ! Seul Firefox supporte les proxies et il n’existe pas de prothèse (<em>polyfill</em>) correspondante. Vous êtes donc libre d’expérimenter avec eux. Vous pouvez créer un projet qui crée une galerie des glaces avec des milliers d’exemplaires pour chaque objet pour rendre le tout inextricable et indébogable, il n’y a aucun risque que ce code puisse passer en production… pour le moment.</p>
<p>Les proxies furent d’abord implémentés en 2010 par Andreas Gal dont le code a été revu par Blake Kaplan. Le comité de standardisation a ensuite totalement revu la conception de cette fonctionnalité. C’est Eddy Bruel qui a implémenté cette nouvelle spécification en 2012.</p>
<p>J’ai implémenté <code>Reflect</code> et ce code a été revu par Jeff Walden. Il sera dans Firefox Nightly à partir de ce week-end (NdT : le week-end du 18-19 juillet 2015). Il ne manque que <code>Reflect.enumerate()</code> qui n’est pas encore implémenté.</p>
<p>La prochaine fois, nous aborderons la fonctionnalité la plus controversée d’ES6, et qui mieux que la personne qui les a implémentées dans Firefox pour vous en parler ? (Re)joignez-nous la semaine prochaine avec Eric Faust, ingénieur Mozilla pour présenter les classes ES6 en détails.</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les générateurs, la suiteurn:md5:63544c30f541b73206ef4da54f7083c72015-07-16T20:45:00+02:002015-07-16T20:31:39+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/07/05/ES6-en-details-%3A-les-collections" hreflang="fr" title="Les collections - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/07/es6-in-depth-generators-continued/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à goofy et Banban pour la relecture !</em></p>
<hr /> <p><em><a href="https://tech.mozfr.org/tag/ES6%20en%20d%C3%A9tails">ES6 en détails</a> 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é).</em></p>
<p>Ravi de vous retrouver pour la suite d’ES6 en détails ! J’espère que vous avez savouré cette petite pause (NdT : qui n’a pas été aussi longue entre les articles traduits ;)). Toutefois, la vie d’un développeur n’est pas rythmée uniquement par le soleil ou la limonade. Il est temps de reprendre où nous nous étions arrêtés. Le sujet de cet article est idéal pour cette reprise.</p>
<p><a href="https://tech.mozfr.org/post/2015/05/23/ES6-en-details-%3A-les-generateurs" hreflang="fr" title="Les générateurs ES6 - tech.mozfr.org">En mai, j’avais écrit un article sur les générateurs</a>, un nouveau genre de fonction introduit avec ES6. J’en parlais comme d’une des fonctionnalités les plus magiques d’ES6 et je disais qu’ils pourraient faire partie intégrante de la programmation asynchrone. J’avais conclus cet article de la façon suivante :</p>
<blockquote><p>Il y a encore à dire sur les générateurs.[…] Cependant, je pense que ça suffira pour ce billet, déjà suffisamment déconcertant. Comme le font les générateurs, il vaut mieux faire une pause et reprendre une autre fois.</p></blockquote>
<p>C’est le moment.
Vous pourrez trouver la première partie de cet article <a href="https://tech.mozfr.org/post/2015/05/23/ES6-en-details-%3A-les-generateurs" hreflang="fr" title="Générateurs ES6 - tech.mozfr.org">ici</a>. Il est probablement préférable de la lire avant celui qui vient. Allez-y, c’est amusant, bon c’est un peu long et déroutant. Mais il y a des chatonmignons !</p>
<h2>Un rapide coup d’œil dans le rétroviseur</h2>
<p>La dernière fois, nous nous sommes concentrés sur le comportement de base des générateurs. C’est peut-être un peu <em>bizarre</em>, mais pas très diffcile à comprendre. Une fonction génératrice n’est pas comparable à une fonction ordinaire. La principale différence, c’est que le corps d’une fonction génératrice ne s’exécute pas d’un bloc en une seule fois. Il s’exécute, morceau par morceau, en prenant une pause à chaque fois qu’il atteint une expression <code>yield</code>.</p>
<p>L’article consacré à la première partie sur les générateurs expliquaient ces différentes notions mais nous n’avions pas déroulé d’exemple illustrant comment tout cela fonctionnait en détails. Allons-y.</p>
<pre>
function* desMots() {
yield "coucou";
yield "monde";
}
for (var mot of desMots()) {
alert(mot);
}
</pre>
<p>Ce fragment de script est assez simple. Que se passerait-il en détails si vous pouviez observer les différents acteurs en jeu ? <em>Ça</em> donnerait un script assez différent qui ressemblerait à :</p>
<pre>
SCÈNE - Intérieur d’un ordinateur, le jour
BOUCLE FOR se tient seule sur scène avec un couvre-chef
et un écritoire, l’air affairé.
BOUCLE FOR
(appelant)
desMots()!
GÉNÉRATEUR apparaît, grand, cuivré, avec une allure d’horloge.
Il a un air sympathique mais est aussi immobile qu’une statue.
BOUCLE FOR
(frappant des mains gentiment)
Très bien ! Maintenant, au travail.
(envers le générateur)
.next() !
GÉNÉRATEUR prend vie.
GÉNÉRATEUR
{value: "coucou", done: false}
Il s’immobilise brusquement dans une pose maladroite.
BOUCLE FOR
alert !
ALERT surgit brusquement, l’air alerte et le souffle court.
Il donne l’impression d’être toujours ainsi.
BOUCLE FOR
Dites « coucou » à l’utilisateur.
ALERT se retourne et court hors de la scène.
ALERT
(hors de la scène, hurlant)
Arrêtez tout !
La page web sur tech.mozfr.org dit :
"coucou"
Quelques secondes plus tard, après une pause,
ALERT surgit à nouveau, traverse la scène vers
BOUCLE FOR et dérape pour s’arrêter.
ALERT
L’utilisateur a dit OK.
BOUCLE FOR
(frappant des mains gentiment)
Très bien ! Au travail.
(se retournant vers GÉNÉRATEUR)
.next() !
GÉNÉRATEUR reprend vie.
GÉNÉRATEUR
{value: "monde", done: false}
Il s’immobilise avec une autre pose, tout aussi gauche.
BOUCLE FOR
alert !
ALERT
(déjà à la course)
Je m’en occupe !
(hors de la scène, hurlant)
Arrêtez tout !
La page web sur tech.mozfr.org dit :
"monde" !
S’en suit à nouveau une pause,
puis ALERT surgit sur scène, l’air découragé
ALERT
L’utilisateur a dit OK mais...
empêchez cette page d’ouvrir
des dialogues supplémentaires.
Il sort, boudant.
BOUCLE FOR
(frappant des mains gentiment)
Très bien ! au travail.
(se retournant vers GÉNÉRATEUR)
.next() !
Pour la troisième fois, GÉNÉRATEUR reprend vie.
GÉNÉRATEUR
(l’air digne)
{value: undefined, done: true}
Sa tête s’affaisse sur sa poitrine, ses yeux s’éteignent peu à peu.
Il ne bougera plus.
BOUCLE FOR
C’est l’heure d’aller déjeuner.
Elle sort.
Après un moment, RAMASSE MIETTES entre, se saisit de GÉNÉRATEUR, sans vie, et l’emporte
en dehors de la scène.
</pre>
<p>Bon, ce n’est pas <em>Hamlet</em>, mais vous avez saisi l’idée.</p>
<p>Comme vous avez pu le constater pendant cette pièce, lorsqu’un objet générateur apparaît pour la première fois, il est interrompu. Il se réveille et travaille un peu, à chaque fois que la méthode <code>.next()</code> est appelée.</p>
<p>L’action est effectuée de façon synchrone et sur un seul thread. Vous noterez qu’à chaque instant, seul un des personnages est en train de jouer. Les personnages ne s’interrompent pas ni ne parlent en même temps que d’autres. Ils parlent chacun leur tour, pendant autant de temps que nécessaire (on dirait du Shakespeare !).</p>
<p>Une variation de ce drame se déroulera à chaque fois qu’un générateur sera utilisé dans une boucle <code>for-of</code>. Cette série d’appels à la méthode <code>.next()</code> n’apparaîtra pas n’importe où dans votre code. Pour cet exemple, je les ai mis sur scène mais pour vous et vos programmes, cela se déroulera en arrière-plan car les générateurs et les boucles <code>for-of</code> sont conçus pour fonctionner ensemble grâce à <a href="http://www.ecma-international.org/ecma-262/6.0/index.html#sec-iterator-interface" hreflang="en" title="Spécification ES2015 : l'interface iterator">l’interface iterator</a>.</p>
<p>Pour résumer ce qu’on a vu jusqu’à présent :</p>
<ul>
<li>Les objets générateurs sont des robots cuivrés qui génèrent des valeurs</li>
<li>Pour programmer un robot, il suffit d’un morceau de code : le corps de la fonction génératrice qui l’a créé.</li>
</ul>
<h2>Comment arrêter un générateur</h2>
<p>Les générateurs possèdent certaines fonctionnalités délicates que je n’ai pas abordées dans la première partie :</p>
<ul>
<li><code>generator.return()</code></li>
<li>l’argument optionnel pour <code>generator.next()</code></li>
<li><code>generator.throw(error)</code></li>
<li><code>yield*</code></li>
</ul>
<p>Je m’étais arrêté sans elles car il est parfois difficile de comprendre pourquoi ces fonctionnalités existent. S’en souvenir précisément et savoir quand les utiliser semble un peu ardu à première vue. C’est assez difficile de s’en souvenir et de s’en rappeler. Cependant, au fur et à mesure qu’on utilise les générateurs et qu’on y réfléchit, on comprend leur utilité et on apprend à savoir quand les mettre en pratique.</p>
<p>Voici une façon de faire que vous avez probablement déjà utilisée :</p>
<pre>
function faireQuelqueChose() {
initialisation();
try {
// ... faire quelque chose ...
} finally {
faireLeMénage();
}
}
faireQuelqueChose();
</pre>
<p>La partie « ménage » s’applique à fermer des connexions ou des fichiers, à libérer des ressources système ou à mettre à jour le DOM pour interrompre une animation « en cours ». Ces étapes sont celles qu’on souhaite toujours avoir, y compris si les actions précédentes ont échoué. C’est pour cette raison qu’elles sont placées dans un bloc <code>finally</code>.</p>
<p>Que se passe-t-il si on applique cela à un générateur ?</p>
<pre>
function* produireDesValeurs() {
initialisation();
try {
// ... générer des valeurs ...
} finally {
faireLeMénage();
}
}
for (var valeur of produireDesValeurs()) {
manipuler(valeur);
}
</pre>
<p>A priori ça a l’air bon. Cependant, il y a une subtilité qui s’est glissée ici : l’appel à <code>manipuler(valeur)</code> ne fait pas partie du bloc <code>try</code>. Si cet appel lève une exception, que se passera-t-il pour notre étape de ménage ?</p>
<p>Que se passe-t-il aussi quand la boucle <code>for-of</code> contient une instruction <code>break</code> ou <code>return</code> ?</p>
<p>Ce bloc de « ménage » s’exécutera tout de même. ES6 s’occupe de tout.</p>
<p>Lorsque nous avons abordé <a href="https://tech.mozfr.org/post/2015/05/20/ES6-en-details-%3A-les-iterateurs-et-la-boucle-for-of" hreflang="fr" title="Les itérateurs et la boucle for-of - tech.mozfr.org">les itérateurs et la boucle for-of</a>, nous avions vu que l’interface iterator contenait une méthode optionnelle <code>.return()</code> que le langage appelait automatiquement à la fin de l’itération, avant que l’itérateur déclare qu’il a fini. Les générateurs supportent cette méthode. Quand on appelle <code>monGénérateur.return()</code>, celui-ci exécute ses blocs <code>finally</code> s’il en a. Ensuite, il finit, de la même façon que si <code>yield</code> avait mystérieusement été transformé en une instruction <code>return</code>.</p>
<p>Vous noterez que la méthode <code>.return()</code> n’est pas appelée automatiquement dans <em>tous</em> les contextes. Elle est uniquement appelée pour les cas utilisant le protocole d’itération. Il est donc possible que le ramasse-miettes récupère le générateur sans que celui-ci n’ait exécuté son bloc <code>finally</code>.</p>
<p>À quoi ressemblerait cette fonctionnalité sur scène ? Le générateur est immobile, interrompu au milieu d’une tâche qui a besoin d’être préparée (par exemple la construction d’un gratte-ciel). Tout à coup, quelqu’un provoque une erreur ! La boucle <code>for</code> l’attrape et la met de côté. La boucle indique au générateur de lancer sa méthode <code>.return()</code>. Le générateur prend son temps, démantèle son échafaudage puis s’éteint. Une fois le générateur terminé, la boucle <code>for</code> reprend l’erreur et le mécanisme habituel de gestion des erreurs peut alors prendre le relai.</p>
<h2>Au tour des générateurs</h2>
<p>Jusqu’à présent, les échanges entre un générateur et l’utilisateur étaient assez unilatérales. Pour changer de l’analogie avec le théâtre, voici un nouvel exemple :
<a href="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/img1.png" title="img1.png"><img src="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/.img1_m.png" alt="img1.png" style="display:block; margin:0 auto;" title="img1.png, juil. 2015" /></a>
C’est l’utilisateur qui dicte la conduite générale. Le générateur exécute le travail à la demande. Les générateurs peuvent être utilisés différemment.</p>
<p>Dans la première partie, j’ai dit que les générateurs pouvaient être utilisés pour programmer de façon asynchrone et qu’ils pourraient réaliser ce qu’on fait actuellement avec des fonctions de rappel (<em>callback</em>) asynchrone ou avec des chaînes de promesses. Vous vous êtes sans doute demandé comment cela pouvait fonctioner. Pourquoi <code>yield</code> est-il suffisant ? (après tout c’est le seul super pouvoir des générateurs) Le code asynchrone ne génère pas des valeurs, il <em>réalise des actions</em>. Il utilise des données de fichiers ou de base de données, il envoie des requêtes vers des serveurs. Enfin, il revient dans la boucle des événements pour attendre que ces processus asynchrones aient fini. Comment faire avec les générateurs ? Sans fonction de rappel, comment le générateur peut-il recevoir des données envoyées depuis des fichiers, des bases de données ou des serveurs ?</p>
<p>Pour avoir une idée de la réponse, imaginez ce qui se passerait si vous aviez un moyen de passer une valeur au générateur avec <code>.next()</code>. Grâce à ce seul changement, nous pourrions avoir un nouveau genre de conversation :
<a href="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/img2.png" title="img2.png"><img src="https://tech.mozfr.org/dotclear/public/images_blog/JavaScript/.img2_m.png" alt="img2.png" style="display:block; margin:0 auto;" title="img2.png, juil. 2015" /></a></p>
<p>En fait, la méthode <code>.next()</code> d’un générateur peut prendre un argument. Encore mieux, l’argument utilisé apparaît dans le générateur comme la valeur renvoyée par l’expression <code>yield</code>. Cela signifie que <code>yield</code> n’est pas une instruction comme <code>return;</code> c’est une expression qui possède une valeur une fois que le générateur reprend.</p>
<pre>
var results = yield getDataAndLatte(request.areaCode);
</pre>
<p>Cette petite ligne fait beaucoup de choses :</p>
<ul>
<li>Elle appelle <code>getDataAndLatte()</code>. Disons que cette fonction renvoie la chaîne “fournissez-moi les données pour la zone…” comme vu dans la capture d’écran.</li>
<li>Elle interrompt le générateur qui génère la chaîne de caractères.</li>
<li>Du temps passe…</li>
<li>Quelqu’un appelle ensuite <code>.next({data: ..., café: ...})</code>. On enregistre cet objet dans une variable locale et on poursuit avec la prochaine ligne de code.</li>
</ul>
<p>Pour illustrer cet exemple avec un contexte, voici le code qui correspond à la conversation précédente :</p>
<pre>
function* handle(request) {
var results = yield getDataAndLatte(request.areaCode);
results.coffee.drink();
var target = mostUrgentRecord(results.data);
yield updateStatus(target.id);
}
</pre>
<p>Vous noterez que <code>yield</code> a toujours la même signification qu’avant : il met le générateur en pause et passe une valeur à l’appelant. Mais les choses ont changé ! Le générateur attend un certain support de la part de l’appelant. Le générateur s’attend à ce que l’appelant agisse comme un assistant.</p>
<p>Les fonctions ordinaires ne ressemblent généralement pas à ça. La plupart du temps, elles existent pour répondre aux besoins de l’appelant. En revanche, les générateurs peuvent être utilisés pour avoir des conversations et cela permet d’avoir de nouvelles relations entre les générateurs et leurs appelants.</p>
<p>À quoi pourrait ressembler cet assistant de générateur ? Il n’est pas nécessaire qu’il soit compliqué. Par exemple, il pourrait ressembler à :</p>
<pre>
function runGeneratorOnce(g, result) {
var status = g.next(result);
if (status.done) {
return; // pfiou !
}
// Le générateur nous a demandé d'aller chercher
// quelque chose et de l'appeler à nouveau
// quand ce serait fait
doAsynchronousWorkIncludingEspressoMachineOperations(
status.value,
(error, nextResult) => runGeneratorOnce(g, nextResult));
}
</pre>
<p>Pour utiliser cet assistant, il suffit de créer un générateur et de l’exécuter une fois, de cette façon :</p>
<pre>
runGeneratorOnce(handle(request), undefined);
</pre>
<p>En mai, j’ai évoqué <code>Q.async()</code> comme un exemple de bibliothèque qui traite les générateurs comme des processus asynchrones et qui les lance automatiquement quand on en a besoin. <code>runGeneratorOnce</code> agit de cette façon. En pratique, les générateurs ne génèreront pas des chaînes de caractères pour indiquer à l’appelant ce qu’il faut faire, ils utiliseront probablement des promesses (objets <code>Promise</code>).</p>
<p>Si vous comprenez comment utiliser les promesses et que vous avez compris comment utiliser les générateurs, vous pouvez essayer de modifier <code>runGeneratorOnce</code> pour qu’il supporte les promesses. Cet exercice n’est pas un exercice facile mais une fois que vous en serez venu à bout, vous serez capable d’écrire des algorithmes asynchrones complexes qui utilisent des promesses et qui sont écrits en quelques lignes. La cerise sur le gateau : aucun <code>.then()</code> ou aucune fonction de rappel (<em>callback</em>) en vue !</p>
<h2>Comment faire exploser un générateur</h2>
<p>Avez-vous remarqué comment <code>runGeneratorOnce</code> gère les erreurs ? Il les ignore complètement !</p>
<p>C’est problématique, nous préfèrerions qu’il existe un moyen de rapporter l’erreur au générateur. Les générateurs permettent de le faire, vous pouvez appeler <code>generator.throw(erreur)</code> plutôt que <code>generator.next(result)</code>. Cela permet de lever une exception avec l’expression <code>yield</code>. Comme pour <code>.return()</code>, le générateur s’éteindra mais si le <code>yield</code> utilisé est contenu dans un bloc <code>try</code>, les instructions des blocs <code>catch</code> et <code>finally</code> seront utilisées afin que le générateur puisse se terminer de façon correcte.</p>
<p>Vous pouvez également modifier <code>runGeneratorOnce</code> pour vous assurer que <code>.throw()</code> est appelé quand c’est nécessaire, c’est un autre exercice intéressant. Attention, gardez à l’esprit que les exceptions levées à l’intérieur de générateurs sont toujours propagées vers l’appelant. <code>generator.throw(erreur)</code> lèvera une erreur directement dans le code appelant sauf si le générateur l’attrape pour la gérer !</p>
<p>Cela complète la liste des cas de figures possibles qui se produisent quand un générateur atteint une expression <code>yield</code> et s’interrompt :</p>
<ul>
<li>Quelqu’un peut appeler <code>generator.next(valeur)</code>. Dans ce cas, le générateur reprend son exécution là où il s’était arrêté.</li>
<li>Quelqu’un peut appeler <code>generator.return()</code>, éventuellement avec une valeur. Dans ce cas, le générateur ne continue pas avec ce qu’il était en train de faire, il exécute uniquement le bloc <code>finally</code>.</li>
<li>Quelqu’un peut appeler <code>generator.throw(erreur)</code>. Dans ce cas le générateur se comporte comme si l’expression <code>yield</code> était un appel à une fonction qui a levé une exception.</li>
<li>Ou alors, personne ne fait rien. Dans ce cas le générateur reste gelé à jamais (oui, il est possible qu’un générateur entre dans un bloc <code>try</code> et n’exécute jamais le bloc <code>finally</code>). Le ramasse-miettes peut récupérer le générateur dans cette situation.</li>
</ul>
<p>Il n’y a pas grande différence avec un appel de fonction classique. Seul <code>.return()</code> est réellement une nouvelle possibilité.</p>
<p>En fait, <code>yield</code> possède beaucoup de points communs avec les appels de fonctions. Lorsque vous appelez une fonction, vous êtes temporairement en attente n’est-ce pas ? C’est la fonction que vous appelez qui est aux commandes, elle peut renvoyer un résultat, lever un exception ou encore boucler infiniment.</p>
<h2>Combiner des générateurs</h2>
<p>Je voudrais vous montrer une dernière fonctionnalité. Imaginons qu’on écrive une fonction génératrice qui concatène deux objets itérables :</p>
<pre>
function* concat(iter1, iter2) {
for (var value of iter1) {
yield value;
}
for (var value of iter2) {
yield value;
}
}
</pre>
<p>ES6 propose un raccourci pour ceci :</p>
<pre>
function* concat(iter1, iter2) {
yield* iter1;
yield* iter2;
}
</pre>
<p>Un expression <code>yield</code> « simple » génère une seule valeur. Une expression <code>yield*</code> consomme un itérateur entier et génère <em>toutes</em> ses valeurs.</p>
<p>Cette même syntaxe permet de résoudre un autre problème : comment appeler un générateur depuis un générateur ? Avec les fonctions classiques, on peut isoler un morceau de code d’une fonction pour le placer dans une fonction séparée et le réutiliser sans changer le comportement. Bien évidemment, on voudra aussi refactoriser des générateurs. Or, pour cela, il nous faut une méthode pour appeler le fragment refactorisé et s’assurer que chaque valeur qui était générée avant est toujours générée avec ce nouveau sous-programme. <code>yield*</code> permet de faire cela :</p>
<pre>
function* générateurExtrait() { ... }
function* fonctionRefactorée() {
...
yield* générateurExtrait();
...
}
</pre>
<p>Vous pouvez voir cela comme une tâche qu’un robot délègue à un autre. Vous pouvez voir ici combien cette idée est importante pour écrire de grands projets utilisant des générateurs et pour garder le code propre et organisé. C’est aussi important que les fonctions pour organiser du code synchrone.</p>
<h2>Exeunt</h2>
<p>Bien, c’est terminé pour les générateurs ! J’espère que vous avez apprécié cette découverte autant que moi !</p>
<p>La semaine prochaine, nous parlerons d’une fonctionnalité ES6 entièrement nouvelle et surprenante. Il s’agit d’un nouveau genre d’objet, subtil et retors : vous pourrez en utiliser un sans même le savoir. Rendez-vous donc la semaine prochaine pour étudier les proxies ES6 en détails.</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les collectionsurn:md5:9b401d363f2958d924ad3f8b92a317432015-07-07T21:30:00+02:002015-07-07T21:30:00+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/06/21/ES6-en-details-%3A-les-symboles" hreflang="fr" title="Les symboles - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/06/es6-in-depth-collections/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à Marine, Banban, Ilphrin et goofy pour la traduction et la relecture !</em></p>
<hr /> <p><em><a href="https://tech.mozfr.org/tag/ES6%20en%20d%C3%A9tails">ES6 en détails</a> 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é).</em></p>
<p>En début de semaine (NdT : le 17 juin 2015), les spécifications ES6, officiellement intitulées <em>ECMA-262, 6th Edition, ECMAScript 2015 Language Specification</em>, étaient sur la dernière ligne droite et ont été approuvées en tant que standard Ecma. Félicitations au TC39 et à tous ceux qui ont contribué. ES6 est publié !</p>
<p>Une autre nouvelle encore plus intéressante : la prochaine mise à jour ne mettra pas six ans à arriver. Le comité de standardisation a annoncé son objectif de lancer une nouvelle version environ tous les 12 mois. <a href="https://github.com/tc39/ecma262" hreflang="en" title="Ecma262 - GitHub">Les propositions pour la septième édition</a> sont déjà dans les cartons.</p>
<p>Dès lors, il est de circonstance que de fêter tout ça en vous parlant de quelque chose que j’attendais depuis longtemps dans JavaScript – et qui pourra encore être amélioré par la suite !</p>
<h2>De la difficulté d’évoluer</h2>
<p>JavaScript n’est pas un langage de programmation comme les autres et parfois cela influence son évolution de façon surprenante.</p>
<p>Les modules ES6 en sont un exemple flagrant. D’autres langages utilisent des systèmes de modules. Racket possède un système de modules remarquable et Python également. Lorsque que le comité de standardisation a décidé d’ajouter des modules à ES6, pourquoi n’a-t-il pas copié un système existant ?</p>
<p>JavaScript est différent car il fonctionne dans les navigateurs web. Les entrées/sorties peuvent prendre un certain temps. Pour cette raison, JavaScript a besoin d’un système de module qui peut charger du code de façon asynchrone. Il ne peut pas non plus se permettre de chercher les modules dans différents répertoires les uns à la suite des autres. Copier un système existant était voué à l’échec : le système de modules ES6 avait besoin de certaines nouveautés.</p>
<p>La façon dont cela a influencé la conception du système est une histoire intéressante mais nous n’allons pas parler des modules aujourd’hui.</p>
<p>Ce billet portera sur ce que le standard ES6 appelle les « collections à clés » (<em>keyed collections</em> en anglais) : <code>Set</code>, <code>Map</code>, <code>WeakSet</code> et <code>WeakMap</code>. Sous de nombreux angles, ces fonctionnalités ressemblent aux différentes <a href="https://fr.wikipedia.org/wiki/Table_de_hachage" hreflang="fr" title="Tables de hachage - Wikipédia">tables de hachage</a> des autres langages. Toutefois, en raison de la nature de JavaScript, le comité de standardisation a dû faire certains compromis intéressants.</p>
<h2>Les collections, pour quoi faire ?</h2>
<p>Celles et ceux qui sont déjà familiers avec JavaScript savent qu’il y a déjà quelque chose de natif qui ressemble aux tables de hachage : les objets.</p>
<p>Un objet (type <code>Object</code>) n’est finalement rien d’autre qu’une collection ouverte de paires de clés-valeurs. Il est possible d’obtenir, de définir et de supprimer des propriétés. On peut itérer sur les propriétés d’un objet. Toutes ces caractéristiques font que les objets permettent de faire ce qu’on attend d’une table de hachage. Alors pourquoi ajouter une nouvelle fonctionnalité ?</p>
<p>Eh bien, de nombreux programmes utilisent les objets pour stocker des paires de clés-valeurs, et pour les programmes où cela fonctionne, il n’est pas nécessaire d’utiliser des <code>Map</code> ou des <code>Set</code>. Cependant, il existe des problèmes bien connus dans ces cas-là :</p>
<ul>
<li>les objets utilisés comme tables de recherche ne peuvent pas avoir de méthodes sans risque de collisions ;</li>
<li>par conséquent les programmes doivent utiliser <code>Object.create(null)</code> au lieu de <code>{}</code> ou faire attention à ne pas interpréter des méthodes natives (telles que <code>Object.prototype.toString</code>) comme des données ;</li>
<li>les clés utilisés pour les propriétés d’un objet sont nécessairement des chaînes de caractères (ou en ES6, des symboles), des objets ne peuvent pas être des clés ;</li>
<li>il n’existe pas de moyen efficace pour connaître le nombre de propriétés d’un objet.</li>
</ul>
<p>On a un nouveau problème avec ES6 : les objets ne sont pas <a href="https://tech.mozfr.org/post/2015/05/20/ES6-en-details-%3A-les-iterateurs-et-la-boucle-for-of" hreflang="fr" title="Les itérateurs et la boucle for-of - Bidouilleux d'Web">itérables</a>, donc ils ne pourront coopérer avec la boucle <code>for-of</code>, l’opérateur <code>...</code>, etc.</p>
<p>J’insiste, il existe de nombreux programmes pour lesquels tout ceci n’est pas important et pour lesquels les objets seront des choix adaptés. Les <code>Map</code> et les <code>Set</code> sont utiles pour d’autres situations.</p>
<p>Ayant été conçues pour éviter les collisions entre les données des utilisateurs et les méthodes natives, les collections ES6 n’exposent pas leurs données comme des propriétés. Cela signifie que des expressions comme <code>obj.clé</code> ou <code>obj[clé]</code> ne peuvent pas être utilisées pour accéder aux données des tables de hachage. Il faudra écrire <code>map.get(clé)</code>. De même, les éléments d’une table de hachage ne sont pas héritées via la chaîne de prototypes.</p>
<p>L’avantage est qu’on peut ajouter des méthodes aux méthodes existantes de <code>Map</code> et <code>Set</code>, que ce soit sur les classes standards ou sur ses propres sous-classes, sans que cela crée de conflit.</p>
<h2><code>Set</code></h2>
<p>Un <code>Set</code> est un ensemble de valeurs. Un ensemble est modifiable et on peut y ajouter ou y supprimer des valeurs. Ils ressemblent aux tableaux mais il y a quelques différences entre les ensembles (<code>Set</code>) et les tableaux (<code>Array</code>).</p>
<p>Tout d’abord, contrairement à un tableau, un ensemble ne contient jamais la même valeur deux fois. Si vous essayez d’ajouter une valeur qui existe déjà, cela n’aura aucun effet.</p>
<pre>
> var desserts = new Set(["cookie","glace","sundae","donut"]);
> desserts.size
4
> desserts.add("cookie");
Set [ "cookie", "glace", "sundae", "donut" ]
> desserts.size
4
</pre>
<p>Cet exemple utilise des chaînes de caractères, mais un <code>Set</code> peut contenir n’importe quel type de valeurs JS. Comme avec les chaînes, ajouter le même objet ou le même nombre plus d’une fois ne modifiera pas l’ensemble.</p>
<p>Ensuite, un <code>Set</code> gardera ses données organisées afin de faciliter les tests d’appartenance :</p>
<pre>
> // vérifier que "zythum" est un mot.
> tableauMots.indexOf("zythum") !== -1 // lent
true
> ensembleMots.has("zythum") // rapide
true
</pre>
<p>Les ensembles ne supportent pas l’indexation :</p>
<pre>
> tableauMots[15000]
"anapanapa"
> ensembleMots[15000] // l'indexation ne fonctionne pas
undefined
</pre>
<p>Voici toutes les operations qu’on peut effectuer sur les ensembles (<code>Set</code>) :</p>
<ul>
<li><code>new Set</code> crée un nouveau <code>Set</code> vide ;</li>
<li><code>new Set(iterable)</code> crée une nouveau <code>Set</code> et le remplit avec les données d’un <a href="https://tech.mozfr.org/post/2015/05/20/ES6-en-details-%3A-les-iterateurs-et-la-boucle-for-of" hreflang="fr" title="Les itérateurs et la boucle for-of - Bidouilleux d'Web">itérable</a> ;</li>
<li><code>set.has(valeur)</code> renvoie <code>true</code> si le <code>Set</code> contient la valeur donnée ;</li>
<li><code>set.add(valeur)</code> ajoute une valeur au <code>Set</code>. Si la valeur existait déjà dans le <code>Set</code>, rien ne se passe ;</li>
<li><code>set.delete(valeur)</code> supprime une valeur du <code>Set</code>. Si la valeur n’existait pas dans ce <code>Set</code>, rien ne se passe. <code>.add()</code> et <code>.delete</code> renvoient tous les deux l’objet <code>Set</code>, il est donc possible de les enchaîner ;</li>
<li><code>set[Symbol.iterator]()</code> renvoie un nouvel itérateur sur les valeurs du <code>Set</code>. C’est cette méthode qui rend les ensembles itérables. Cela signifie que vous pouvez écrire <code>for (v of set) {...}</code> et ainsi de suite ;</li>
</ul>
<ul>
<li> <code>set.forEach(f)</code> sera plus facilement compris avec un peu de code, c’est un raccourci pour :
<pre>
for (let value of set)
f(value, value, set);
</pre>
Cette méthode est analogue à la méthode <code>.forEach()</code> utilisée pour les tableaux ;
</li>
</ul>
<ul>
<li><code>set.clear()</code> retire toutes les valeurs de l’ensemble ;</li>
<li><code>set.keys()</code>, <code>set.values()</code> et <code>set.entries()</code> renvoient différents itérateurs. Ceux-ci sont fournis pour obtenir une compatibilité avec <code>Map</code>. Nous les décrirons dans la suite de cet article.</li>
</ul>
<p>Parmi toutes ces fonctionnalités, le constructeur <code>new Set(iterable)</code> est un vrai couteau suisse car il permet de manipuler des structures de données entières. Vous pouvez l’utiliser pour convertir un tableau (<code>Array</code>) en un ensemble (<code>Set</code>) et éliminer les doublons avec une seule ligne de code.
Vous pouvez également lui passer un générateur, le constructeur utilisera le générateur jusqu’à épuisement et collectera les valeurs dans l’ensemble construit. Le constructeur permet également d’obtenir une copie d’un objet Set existant.</p>
<p>Dans le billet précédent, j’avais prévenu que j’allais me plaindre sur les collections ES6. Pour commencer, voici quelques méthodes qui pourraient tout à fait améliorer les objet Set et qui pourraient être ajoutées par la suite dans le standard :</p>
<ul>
<li>les méthodes utilitaires qu’on a déjà sur les tableaux : <code>.map()</code>, <code>.filter()</code>, <code>.some()</code> et <code>.every()</code> ;</li>
<li>des méthodes <code>set1.union(set2)</code> et <code>set1.intersection(set2)</code> qui ne modifient pas les objets sur lesquels elles sont appelées ;</li>
<li>des méthodes qui peuvent agir sur plusieurs valeurs à la fois : <code>set.addAll(iterable)</code>, <code>set.removeAll(iterable)</code>, et <code>set.hasAll(iterable)</code>.</li>
</ul>
<p>Bonne nouvelle ! Toutes ces méthodes peuvent être implémentées efficacement en utilisant les outils fournis par ES6.</p>
<h2><code>Map</code></h2>
<p>Une <code>Map</code> est une collection de paires de clés-valeurs. Voici ce qu’on peut faire avec une <code>Map</code> :</p>
<ul>
<li><code>new Map</code> renvoie un nouveau tableau associatif vide ;</li>
<li><code>new Map(paires)</code> crée un nouveau tableau associatif et le remplit avec les paires contenues dans l’objet <code>paires</code>. Cet objet peut être un objet <code>Map</code>, un tableau qui contient des tableaux de deux éléments, un générateur qui génère des tableaux à deux éléments, etc.</li>
<li><code>map.size</code> fournit le nombre de paires contenues dans le tableau associatif ;</li>
<li><code>map.has(clé)</code> permet de tester si une clé est présente (semblable à l’expression <code>clé in obj</code>) ;</li>
<li><code>map.get(clé)</code> fournit la valeur associée à une clé donnée. S’il n’y a pas de valeur pour cette clé, la valeur renvoyée sera <code>undefined</code> ;</li>
<li><code>map.set(clé, valeur)</code> ajoute un nouvelle paire dans le tableau associatif, si un élément existe déjà pour cette clé, cela modifiera la valeur associée (semblable à <code>obj[clé] = valeur</code>) ;</li>
<li><code>map.delete(clé)</code> supprime une paire donnée (semblable à <code>delete obj[clé]</code>) ;</li>
<li><code>map.clear()</code> supprime toutes les paires contenues dans le tableau associatif ;</li>
<li><code>map[Symbol.iterator]()</code> renvoie un itérateur qui parcourt les paires du tableau associatif. L’itérateur représente chaque élément comme un tableau <code>[clé, valeur]</code> ;</li>
</ul>
<ul>
<li> <code>map.forEach(f)</code> fonctionne comme ça :
<pre>
for (let [clé, valeur] of map)
f(valeur, clé, map);
</pre>
L’ordre des arguments est pris de cette façon pour conserver l’analogie avec <code>Array.prototype.forEach()</code> ;
</li>
</ul>
<ul>
<li><code>map.keys()</code> renvoie un itérateur qui parcourt les clés du tableau associatif ;</li>
<li><code>map.values()</code> renvoie un itérateur qui parcourt les valeurs du tableau associatif ;</li>
<li><code>map.entries()</code> renvoie un itérateur qui parcourt les paires du tableau associatif de la même façon que <code>map[Symbol.iterator]()</code>. Les deux sont équivalentes et sont en fait synonymes.</li>
</ul>
<p>De quoi pourrait-on se plaindre ici ? Voici quelques fonctionnalités, qui ne font pas partie d’ES6 et qui pourraient, à mon avis, être utiles :</p>
<ul>
<li>Un outil pour gérer les valeurs par défaut (par exemple Python a <code>collections.defaultdict</code>) ;</li>
<li>Une fonction utilitaire <code>Map.fromObject(obj)</code> qui permettrait de créer des tableaux associatifs en utilisant des littéraux objets.</li>
</ul>
<p>De même, il est facile d’ajouter ces fonctionnalités.</p>
<p>OK. Revenons maintenant au début de l’article, j’y expliquais que, pour certains aspects, JavaScript était unique et que cela avait un impact sur la conception des fonctionnalités de ce langage. J’ai trois exemples pour expliquer cela, voici les deux premiers.</p>
<h2>Les différences de JavaScript, première partie : Des tables de hachage sans code de hachage ?</h2>
<p>Voici une fonctionnalité que les collections ES6 ne supportent pas.
Imaginons qu’on ait un ensemble (<code>Set</code>) qui contient des objets représentant des URL :</p>
<pre>
var urls = new Set;
urls.add(new URL(location.href)); // deux objets URL
urls.add(new URL(location.href)); // est-ce que ce sont les mêmes ?
alert(urls.size); // 2
</pre>
<p>Ces deux URL pourraient parfaitement être considérées comme égales. Elles possèdent toutes les deux les mêmes champs. Cependant, en JavaScript, ces deux objets sont distincts et il n’y a pas de méthode pour surcharger cette notion d’égalité.</p>
<p>D’autres langages permettent de le faire : Java, Python, Ruby dans lesquels les classes peuvent surcharger l’égalité. De nombreuses implémentations Scheme permettent de créer tables de hachage dont chacune peut utiliser une relation d’égalité sur mesure. C++ permet de faire les deux.</p>
<p>Cependant, tous ces mécanismes exposent la fonction de hachage utilisée par défaut par le système et il faut implémenter des fonctions de hachage personnalisées. Le comité de standardisation a choisi de ne pas exposer les codes de hachage pour JavaScript (du moins, pas encore) en raison de questions ouvertes sur l’interopérabilité et la sécurité. Ces deux préoccupations sont moindres pour les autres langages.</p>
<h2>Les différences de JavaScript, deuxième partie : Prévisibilité et surprises !</h2>
<p>On pourrait penser que pour un ordinateur, c’est la moindre des choses que d’avoir un comportement déterministe. Malgré ça, quand j’explique que les boucles qui parcourent les objets <code>Map</code> et <code>Set</code> le font dans l’ordre d’insertion des éléments, j’obtiens souvent des regards surpris. Pourtant, c’est tout à fait déterministe.</p>
<p>Nous sommes habitués à un certain arbitraire pour les tables de hachage. Nous nous y sommes habitués. Cependant, il y a quelques bonnes raisons d’éviter ce côté arbitraire. Comme je l’écrivais en 2012 :</p>
<ul>
<li>Il est démontré que certains programmeurs sont surpris voire confus par cet ordre de parcours arbitraire <a href="http://stackoverflow.com/questions/2453624/unsort-hashtable" hreflang="en">[1]</a><a href="http://stackoverflow.com/questions/1872329/storing-python-dictionary-entries-in-the-order-they-are-pushed" hreflang="en">[2]</a><a href="https://groups.google.com/group/comp.lang.python/browse_thread/thread/15f3b4a5cd6221b1/1b6621daf5d78d73" hreflang="en">[3]</a><a href="http://bytes.com/topic/c-sharp/answers/439203-hashtable-items-order" hreflang="en">[4]</a><a href="http://stackoverflow.com/questions/1419708/how-to-keep-the-order-of-elements-in-hashtable" hreflang="en">[5]</a><a href="http://stackoverflow.com/questions/7105540/hashtable-values-reordered" hreflang="en">[6]</a> ;</li>
<li>L’ordre d’énumération des propriétés n’est pas spécifié par ECMAScript. Malgré tout, les principales implémentations semblent avoir convergé vers l’ordre d’insertion, afin d’avoir une certaine compatibilité sur le Web. Cela devient donc préoccupant que le TC39 ne spécifie pas un ordre d’itération déterministe : « le Web ira de l’avant et construira sa règle » <a href="https://mail.mozilla.org/pipermail/es-discuss/2012-February/020576.html" hreflang="en" title="Février 2012 - es-discuss">[7]</a> ;</li>
<li>L’ordre d’itération des tables de hachage peut exposer certains fragments des codes de hachage. Cela implique que l’implémentation de la fonction de hachage prenne en compte ces éléments de sécurité. Il ne faut pas, par exemple, que l’adresse d’un objet puisse être obtenue depuis les fragments du code de hachage (révéler les adresses des objets à du code ECMAScript tierce ne serait a priori pas exploitable mais constituerait tout de même un bogue de sécurité majeur).</li>
</ul>
<p>Lorsque cela a été discuté en février 2012, <a href="https://mail.mozilla.org/pipermail/es-discuss/2012-February/020541.html" hreflang="en" title="Février 2012 - Liste de diffusion es-discuss">j’étais favorable à un ordre d’itération arbitraire</a>. Ensuite, j’ai mis en place une expérience afin de démontrer que suivre l’ordre d’insertion des éléments ralentirait trop les tables de hachage. J’ai écrit quelques microbenchmarks C++.</p>
<p><a href="https://wiki.mozilla.org/User:Jorend/Deterministic_hash_tables" hreflang="en" title="Résultats - wiki.mozilla.org / Jorendorff">Les résultats que j’ai obtenus m’ont surpris</a> et c’est pourquoi nous avons aujourd’hui des tables de hachage JavaScript qui fonctionnent en utilisant l’ordre d’insertion des éléments !</p>
<h2>De bonnes raisons d’utiliser des collections à liens faibles</h2>
<p>Dans <a href="https://tech.mozfr.org/post/2015/06/21/ES6-en-details-%3A-les-symboles" hreflang="fr" title="Les symboles - Bidouilleux d'Web">l’article précédent</a>, nous avons discuté d’un exemple où il était question d’une bibliothèque d’animation JS. Nous voulions stocker un booléen pour chaque objet du DOM, de cette façon :</p>
<pre>
if (element.isMoving) {
smoothAnimations(element);
}
element.isMoving = true;
</pre>
<p>Rajouter une propriété de cette façon est une mauvaise idée. Nous l’avons expliqué dans le billet précédent.</p>
<p>Nous avons vu que nous pouvions résoudre ce problème à l’aide de symboles. Est-ce qu’il serait possible d’utiliser des ensembles (<code>Set</code>) ? Cela pourrait ressembler à :</p>
<pre>
if (movingSet.has(element)) {
smoothAnimations(element);
}
movingSet.add(element);
</pre>
<p>Il y a un inconvénient : les objets <code>Map</code> et <code>Set</code> gardent une référence forte pour chaque clé-valeur qu’ils contiennent. Cela signifie que si l’élément du DOM est retiré, le ramasse-miettes (NdT : <em>garbage collection</em> ou <em>GC</em> en anglais) ne pourra récupérer la mémoire occupée tant que l’élément n’est pas explicitement retiré de <code>movingSet</code>. Généralement, les bibliothèques qui imposent aux utilisateurs de « nettoyer » après leur passage remportent au mieux un succès mitigé. Si les références ne sont pas retirées, on pourrait avoir des fuites mémoire.</p>
<p>ES6 apporte une solution surprenante à ce problème : il suffit que <code>movingSet</code> soit un <code>WeakSet</code> plutôt qu’un <code>Set</code> et le problème de fuite mémoire est résolu !</p>
<p>Cela signifie qu’il est possible de résoudre ce problème en utilisant des symboles ou des collections à références faibles. Quelle est la meilleure solution ? Discuter ici des avantages et inconvénients de chacune de ces méthodes rendrait ce billet trop long. En résumé, si vous pouvez utiliser un seul symbole pour la durée de vie de la page web, faites-le. En revanche, si vous arrivez à un état où vous accumulez des symboles plus ou moins éphémères, pensez à utiliser les objets WeakMap pour éviter les fuites mémoire.</p>
<h2><code>WeakMap</code> et <code>WeakSet</code></h2>
<p><code>WeakMap</code> et <code>WeakSet</code> sont spécifiés pour se comporter exactement comme <code>Map</code> et <code>Set</code> mais comportent quelques restrictions :</p>
<ul>
<li><code>WeakMap</code> supporte uniquement <code>new</code>, <code>.has()</code>, <code>.get()</code>, <code>.set()</code>, et <code>.delete()</code> ;</li>
<li><code>WeakSet</code> supporte uniquement <code>new</code>, <code>.has()</code>, <code>.add()</code>, et <code>.delete()</code> ;</li>
<li>Les valeurs enregistrées dans un <code>WeakSet</code> et les clés enregistrées dans un <code>WeakMap</code> doivent être des objets.</li>
</ul>
<p>Note : aucune des collections à références faibles n’est itérable. Il est impossible d’obtenir des éléments d’une telle collection autrement qu’en utilisant la clé de l’élément qu’on souhaite récupérer.</p>
<p>Ces restrictions finement mises en œuvre permettent au ramasse-miettes de collecter les objets morts, même s’ils étaient utilisés dans ces collections. L’effet obtenu est semblable à celui qu’on pourrait avoir avec des références faibles ou des dictionnaires à clés faibles mais ES6 permet de bénéficier de ces avantages <em>sans exposer le fait que le ramasse-miettes agisse sur les scripts</em>.</p>
<h2>Les différences de JavaScript, troisième partie : Masquer le non-déterminisme du ramasse-miettes</h2>
<p>Sous le capot, les collections à références faibles sont implémentées comme des tableaux d’<a href="https://en.wikipedia.org/wiki/Ephemeron" hreflang="en" title="Ephemeron - Wikipédia">ephemeron</a> (voir aussi <a href="http://www.jucs.org/jucs_14_21/eliminating_cycles_in_weak/jucs_14_21_3481_3497_barros.pdf" hreflang="en" title="Eliminating Cycles in Weak Tables - JUSC">cet article</a>).</p>
<p>En résumé, un <code>WeakSet</code> ne conserve pas de référence forte vers l’objet qu’il contient. Lorsqu’un objet d’un <code>WeakSet</code> est récupéré par le ramasse-miettes, il est simplement retiré du <code>WeakSet</code>. <code>WeakMap</code> fonctionne de façon similaire. Ces objets ne gardent pas de références fortes vers les clés. Si une clé est « vivante », la valeur associée l’est aussi.</p>
<p>Pourquoi avoir ces restrictions ? Ne suffirait-il pas d’ajouter le mécanisme de référence faible à JavaScript ?</p>
<p>Là encore, le comité de standardisation a été très réticent en ce qui concerne cette fonctionnalité. En effet, il faut éviter au maximum d’exposer un comportement non-déterministe pour les scripts. Des problèmes de compatibilité entre les navigateurs seraient un drame pour le développement web. Utiliser des références faibles expose certains détails d’implémentation du ramasse-miette, lequel dépend nécessairement de la plate-forme spécifique utilisée. Bien entendu, les applications ne devraient pas dépendre de détails propre à la plate-forme mais lorsqu’on utilise les références faibles, il devient difficile de savoir à quel point le script repose sur le fonctionnement du ramasse-miettes.</p>
<p>En comparaison, les collections à références faibles ES6 possèdent beaucoup moins de fonctionnalités que les références faibles mais ces fonctionnalités sont solides comme le roc. Le fait qu’une clé ou une valeur ait été collectée n’est pas observable en tant que tel. Les applications ne peuvent donc pas dépendre du comportement du ramasse-miettes, volontairement ou accidentellement.</p>
<p>Ce scénario, propre au Web, a mené vers ce choix de conception qu’on peut trouver surprenant mais qui a, <em>in fine</em>, amélioré JS.</p>
<h2>Quand puis-je commencer à utiliser ces fonctionnalités ?</h2>
<p>Ces quatre classes de collection sont disponibles dans Firefox, Chrome, Microsoft Edge et Safari. Pour que cela fonctionne dans d’anciens navigateurs, il faut utiliser une prothèse comme <a href="https://github.com/WebReflection/es6-collections" hreflang="en" title="es6-collections - GitHub">es6-collections</a>.</p>
<p><code>WeakMap</code> fut d’abord implémenté dans Firefox par Andreas Gal avant qu’il devienne CTO à Mozilla. Tom Schuster a implémenté <code>WeakSet</code>. Quant à moi j’ai implémenté <code>Map</code> et <code>Set</code>. Merci à Tooru Fujisawa d’avoir contribué avec plusieurs patches.</p>
<p>C’est le temps d’une courte pause pour ES6 en détails. Nous avons déjà parcouru beaucoup de chemin mais les fonctionnalités les plus puissantes d’ES6 restent à venir. Rendez-vous le 9 juillet pour un prochain article ! (NdT : le 09 juillet sera la date de la publication de l’article sur https://hacks.mozilla.org, la traduction habituelle suivra quelques jours après sur https://tech.mozfr.org :))</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les symbolesurn:md5:cc1c54c12d21edf743d7f67e06d4756c2015-06-21T19:30:00+02:002015-06-21T19:30:00+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/06/10/ES6-en-details-%3A-les-fonctions-flechees" hreflang="fr" title="Les fonctions fléchées - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/06/es6-in-depth-symbols/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à Marine, Mentalo, Banban, Thegennok et goofy pour la traduction et la relecture !</em></p>
<hr /> <p><em><a href="https://tech.mozfr.org/tag/ES6%20en%20d%C3%A9tails">ES6 en détails</a> 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é).</em></p>
<p>Que sont les symboles ES6 ?</p>
<p>Les symboles ne sont pas des logos.
Ce ne sont pas de petits dessins que vous utilisez dans votre code.</p>
<pre>
let = ♪ × ♫; // SyntaxError
</pre>
<p>Il ne s’agit pas non plus d’un procédé littéraire utilisé pour représenter quelque chose d’autre.</p>
<p>Malgré une légère consonance, les symboles ne sont pas non plus des cymbales.</p>
<p>Alors que sont les symboles ?</p>
<h2>Rencontre du septième type</h2>
<p>Depuis la première standardisation de JavaScript en 1997, il y a toujours eu six types de variables. Jusqu’à ES6, chaque valeur d’un programme écrit en JS rentrait dans l’une de ces catégories :</p>
<ul>
<li>Undefined</li>
<li>Null</li>
<li>Boolean</li>
<li>Number</li>
<li>String</li>
<li>Object</li>
</ul>
<p>Chaque type contient un ensemble de valeurs. Les cinq premiers ensembles sont finis. Il y a, bien sûr, seulement deux valeurs booléennes, <code>true</code> et <code>false</code>, on ne va pas en inventer d’autres. Il y a bien plus de valeurs de type Number et String. D’après le standard, il est possible de représenter 18 437 736 874 454 810 627 nombres différents (en incluant <code>NaN</code>, le nombre qui a pour nom « <em>Not a Number</em> », « Pas un Nombre » en français). Ce n’est rien comparé au nombre de chaînes de caractères (String) possible, qui vaut d’après moi (2<sup>144,115,188,075,855,872</sup> − 1) ÷ 65,535… mais j’ai peut-être mal compté.</p>
<p>L’ensemble des valeurs pour les objets (type Object) est infini. Chaque objet est unique, un peu comme un flocon de neige. Chaque fois que vous ouvrez une page web, de nombreux objets sont créés.</p>
<p>Les symboles ES6 sont des valeurs, mais ce ne sont pas des chaînes de caractères (String). Ce ne sont pas des objets. C’est une nouveauté : un septième type de valeurs.</p>
<p>Nous allons prendre un exemple pour les étudier.</p>
<h2>Un simple petit booléen</h2>
<p>Parfois, on aimerait tout simplement pouvoir stocker des données additionnelles dans un objet JavaScript appartenant à quelqu’un d’autre.</p>
<p>Par exemple, supposons que vous écriviez une bibliothèque JS qui utilise les transitions CSS pour déplacer des éléments DOM sur l’écran. Vous avez sans doute remarqué qu’appliquer plusieurs transitions CSS à un seul élément div ne fonctionne pas. C’est moche, ça « saute » sans arrêt. Vous pensez pouvoir arranger ça, mais d’abord vous devez trouver un moyen de savoir si un élément est déjà en mouvement.</p>
<p>Comment y arriver ?</p>
<p>Une solution serait d’utiliser une API CSS pour demander au navigateur si l’élément bouge. Mais cela paraît disproportionné. Votre bibliothèque devrait déjà savoir quel élément se déplace : c’est bien le code de la bibliothèque qui a mis l’élément en mouvement au début !</p>
<p>Ce que vous voulez vraiment, c’est garder une trace des éléments qui bougent. Vous pourriez définir un tableau contenant les éléments qui bougent. Chaque fois que votre bibliothèque anime un élément, vous pourriez alors vérifier si cet élément existe déjà dans le tableau.</p>
<p>Hmm… Cela entraînera donc une recherche linéaire, ce qui peut s’avérer lent si le tableau est long.</p>
<p>En fait, ce que vous voulez vraiment, c’est pouvoir associer un état à l’élément :</p>
<pre>
if (element.isMoving) {
smoothAnimations(element);
}
element.isMoving = true;
</pre>
<p>Cela peut éventuellement poser plusieurs problèmes. Tous ces problèmes reposent sur le fait que votre code n’est pas le seul élément à utiliser le DOM.</p>
<ol>
<li>Un autre code utilisant <code>for-in</code> ou <code>Objects.keys()</code> pourra trébucher sur la propriété que vous avez créée.</li>
<li>Un concepteur ingénieux travaillant sur la bibliothèque peut avoir déjà pensé à cette technique, ce qui pourra détériorer les interactions avec votre bibliothèque.</li>
<li>Un autre concepteur pourra penser à cela plus tard, ce qui pourra détériorer les interactions avec votre bibliothèque plus tard.</li>
<li>Le comité de standardisation pourrait décider d’ajouter une méthode <code>.isMoving()</code> à tous les éléments, et là vous seriez vraiment mal !</li>
</ol>
<p>Évidemment, vous pouvez résoudre les trois derniers points en choisissant une chaîne tellement complexe et folle que personne d’autre ne pourra jamais nommer quoi que ce soit de la même façon :</p>
<pre>
if (element.__$jorendorff_animation_library$NON_PAS_TOUCHE_A_CETTE_PROPRIETE_BISOUS$isMoving__) {
smoothAnimations(element);
}
element.__$jorendorff_animation_library$NON_PAS_TOUCHE_A_CETTE_PROPRIETE_BISOUS$isMoving__ = true;
</pre>
<p>Cette solution ne mérite même pas un coup d’œil.</p>
<p>Vous pourriez aussi générer un nom quasiment unique pour cette propriété grâce à la cryptographie :</p>
<pre>
// obtenir un gloubi-boulga de 1024 caractères Unicode
var isMoving = SecureRandom.generateName();
...
if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;
</pre>
<p>La syntaxe <code>objet[nom]</code> permet littéralement d’utiliser n’importe quelle chaîne pour dénommer une propriété, ceci fonctionnera et les collisions seront virtuellement impossibles. De plus, votre code est lisible.</p>
<p>En revanche, bonne chance pour le débogage. Chaque fois que vous utiliserez <code>console.log()</code> avec cette propriété, vous obtiendrez une longue chaîne illisible. Et si vous avez besoin de plusieurs propriétés comme celle-ci ? Comment allez-vous les gérer ? Elles auront des noms différents à chaque fois que la page sera rechargée.</p>
<p>Pourquoi est-ce aussi compliqué ? On veut juste un petit booléen !</p>
<h2>Tout un symbole</h2>
<p>Les symboles sont des valeurs que les programmes peuvent créer et utiliser comme des clés de propriétés sans risquer de rentrer en collision avec les noms déjà utilisés.</p>
<pre>
var monSymbole = Symbol();
</pre>
<p>Appeler <code>Symbol()</code> crée un nouveau symbole, c’est-à-dire une valeur qui n’est égale à aucune autre valeur.
Un symbole peut être utilisé pour être la clé d’une propriété, comme une chaîne ou un nombre. Étant donné qu’un symbole n’est égal à aucune chaîne, une propriété désignée par un symbole n’entrera pas en collision avec une autre propriété.</p>
<pre>
obj[monSymbole] = "ok !"; // garanti sans collision
console.log(obj[monSymbole]); // ok !
</pre>
<p>Voici comment vous pouvez utiliser un symbole pour la situation dont nous avons discuté ci-avant :</p>
<pre>
// On crée un symbole unique
var isMoving = Symbol("isMoving");
...
if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;
</pre>
<p>Quelques notes à propos de ce code :</p>
<ul>
<li>La chaîne <code>"isMoving"</code> dans <code>Symbol("isMoving")</code> est appelée une description. C’est utile lors du débogage. Elle est visible quand on affiche le symbole sur la console via <code>console.log()</code>, quand on le convertit en chaîne avec <code>.toString()</code>, et sans doute aussi dans les messages d’erreurs. C’est tout.</li>
<li><code>element[isMoving]</code> est une propriété dont la clé est un symbole. C’est seulement une propriété dont le nom est un symbole au lieu d’être une chaîne. À part ça, c’est une propriété tout à fait classique.</li>
<li>Comme pour les éléments d’un tableau, il est impossible d’accéder aux propriétés dont la clé est un symbole avec la syntaxe littérale utilisant le point (<code>objet.nom</code>). Il faut utiliser les crochets.</li>
<li>Il est extrêmement simple d’accéder à une propriété dont la clé est un symbole si vous connaissez le symbole. L’exemple ci-dessus montre comment créer <code>element[isMoving]</code> et y accéder. On pourrait aussi tester <code>if(isMoving in element)</code> voire supprimer <code>element[isMoving]</code> si nécessaire.</li>
<li>D’un autre côté, tout ceci n’est possible que si <code>isMoving</code> appartient à la portée. Ainsi les symboles permettent une encapsulation faible : un module qui crée quelques symboles pour lui-même peut les utiliser sur n’importe quel objet, <strong>sans craindre les collisions avec des propriétés créées par un autre code</strong>.</li>
</ul>
<p>Les symboles ont été conçus pour éviter les collisions. Pour cette raison, les méthodes classiques pour explorer les objets ignorent les clés qui sont des symboles. La boucle <code>for-in</code>, par exemple, itérera uniquement sur les clés d’un objet qui sont des chaînes de caractères. Les clés qui sont des symboles seront ignorées. Il en va de même pour <code>Object.keys(obj)</code> et <code>Object.getOwnPropertyNames(obj)</code>. Cependant, les symboles ne sont pas privés ou invisibles : la nouvelle API, <code>Object.getOwnPropertySymbols(obj)</code>, liste les clés d’un objet qui sont des symboles. Une autre API, <code>Reflect.ownKeys(obj)</code>, renvoie les clés qui sont des chaînes et des symboles (cette API sera l’objet d’un billet détaillé à venir).</p>
<p>Les différents frameworks et bibliothèques auront vraisemblablement de nombreux cas d’utilisations pour les symboles. Comme nous allons le voir par la suite, le langage lui-même tire parti des symboles pour différents scénarios.</p>
<h2>Mais qu’est-ce qu’un symbole ?</h2>
<pre>
> typeof Symbol()
"symbol"
</pre>
<p>Les symboles ne ressemblent à aucun autre type pré-existant.</p>
<p>Une fois qu’ils sont créés, ils sont immuables. Il est impossible de définir des propriétés sur eux (si vous essayez en mode strict, vous aurez une TypeError). Ils peuvent être utilisés comme noms pour des propriétés. En ce sens, ils ressemblent à des chaînes de caractères.</p>
<p>D’un autre côté, chaque symbole est unique et se distingue des autres symboles (y compris de ceux qui ont la même
description) et on peut facilement en créer des nouveaux. En ce sens, ils ressemblent à des objets.</p>
<p>Les symboles ES6 sont semblables <a href="https://en.wikipedia.org/wiki/Symbol_%28programming%29" hreflang="en" title="Symbol - Wikipédia">aux symboles traditionnels</a> qu’on peut trouver dans les langages tels que Lisp et Ruby. Cependant, ils ne sont pas si profondément ancrés dans les langages. En Lisp, tous les identifiants sont des symboles. En JS, la plupart des identifiants et la plupart des clés restent des chaînes de caractères, les symboles ne sont qu’une option supplémentaire.</p>
<p>Attention, à la différence des symboles dans les autres langages, les symboles JS ne peuvent pas être convertis automatiquement en chaînes de caractères. Si vous essayez de concaténer un symbole et une chaîne de caractères, vous obtiendrez une exception TypeError.</p>
<pre>
> var sym = Symbol(">3");
> "votre symbole est " + sym
// TypeError: can't convert symbol to string
> `votre symbole est ${sym}`
// TypeError: can't convert symbol to string
</pre>
<p>Pour éviter cela, on peut convertir le symbole en une chaîne de façon explicite avec <code>String(sym)</code> ou <code>sym.toString()</code>.</p>
<h2>Trois ensembles de symboles</h2>
<p>Il y a trois façons d’obtenir un symbole :</p>
<ul>
<li><strong>Appeler <code>Symbol()</code></strong>. Comme nous l’avons vu avant, cette fonction renvoie un nouveau symbole à chaque fois qu’elle est appelée.</li>
<li><strong>Appeler <code>Symbol.for(string)</code></strong>. Cela permet d’accéder à un ensemble de symboles existants, appelé registre des symboles. À la différence des symboles uniques définis avec <code>Symbol()</code>, les symboles appartenant au registre sont partagés. Si vous invoquez <code>Symbole.for("chat")</code> trente fois, vous obtiendrez toujours le même symbole à chaque fois. Le registre peut s’avérer utile quand plusieurs pages web, ou plusieurs modules au sein d’une même page web ont besoin de partager un symbole.</li>
<li><strong>Utiliser les symboles « connus » définis par le standard</strong> tels que <code>Symbol.iterator</code>. Quelques symboles sont définis dans le standard ECMAScript, chacun possède une raison d’être précise.</li>
</ul>
<p>Si vous avez encore des doutes sur l’utilité des symboles, la dernière catégorie est intéressante car elle illustre comment les symboles ont déjà pu être utilisés en pratique.</p>
<h2>Comment ES6 utilise-t-il les symboles connus ?</h2>
<p>On a déjà vu un cas utilisation des symboles par ES6 : éviter les collisions avec du code existant. Il y a quelques semaines, dans <a href="https://tech.mozfr.org/post/2015/05/20/ES6-en-details-%3A-les-iterateurs-et-la-boucle-for-of" hreflang="fr" title="Les itérateurs et la boucle for-of - tech.mozfr.org">le billet sur les itérateurs</a>, on a vu que la boucle <code>for (var item of monTableau)</code> démarre en appelant <code>monTableau[Symbol.iterator]()</code>. J’ai mentionné que cette méthode aurait pu appeler <code>monTableau.iterator()</code> mais qu’il valait mieux utiliser un symbole pour respecter la rétrocompatibilité.</p>
<p>Maintenant que nous connaissons mieux les symboles, il est plus simple de comprendre pourquoi cela a été fait et ce que ça signifie.</p>
<p>Voici quelques exemples des autres endroits où ES6 utilise les symboles connus (ces fonctionnalités ne sont pas encore implémentées dans Firefox).</p>
<ul>
<li><strong>Rendre <code>instanceof</code> extensible</strong>. Avec ES6, l’expression <code>objet instanceof constructeur</code> est définie comme l’appel d’une méthode du constructeur : <code>constructeur[Symbol.hasInstance](objet)</code>. Cela signifie qu’on peut étendre <code>instanceof</code>.</li>
<li><strong>Éliminer les conflits entre les nouvelles fonctionnalités et du code ancien</strong>. On approche ici de la magie noire mais on a découvert que certaines méthodes ES6 pour les tableaux empêchaient certains sites de fonctionner simplement en étant là. D’autres standards du Web ont eu les mêmes problèmes : ajouter de nouvelles méthodes casse les sites existants. Cependant, cette casse est causée par ce qu’on appelle les portées dynamiques. Pour cette raison, ES6 a introduit un symbole spécial : <code>Symbol.unscopables</code>. Celui-ci peut être utilisé par les standard du Web pour éviter que certaines méthodes ne soient impactées par les portées dynamiques.</li>
<li><strong>Supporter de nouvelles sortes de correspondances pour les chaînes de caractères</strong>. Avec ES5, <code>str.match(monObjet)</code> tentait de convertir <code>monObjet</code> en une RegExp. Avec ES6, le moteur vérifie d’abord si <code>monObjet</code> possède une méthode <code>monObjet[Symbol.match](str)</code>. Cela signifie que les bibliothèques peuvent fournir des classes d’analyse de chaînes qui fonctionnent partout où on peut utiliser des objets RegExp.</li>
</ul>
<p>Chacun de ces cas d’utilisation est relativement restreint et, pour mon code de tous les jours, ça peut être difficile de voir où cela aura un grand impact. Si on prend un peu de recul, c’est plus intéressant : les symboles « connus » de JavaScript peuvent être vus comme une version améliorée des doubles tirets (par exemple <code>__variablePrivée</code>) présents en PHP et Python. Le standard pourra les utiliser à l’avenir pour ajouter de nouveaux éléments au langage, sans risquer de collision avec votre code.</p>
<h2>Quand puis-je commencer à utiliser les symboles ES6 ?</h2>
<p>Les symboles sont implémentés dans Firefox 36 et Chrome 38. Je les ai moi-même implémentés dans Firefox personnellement, donc si vos symboles se comportent comme des cymbales, vous savez qui contacter.</p>
<p>Pour les navigateurs qui n’ont pas encore implémenté les symboles ES6, vous pouvez utiliser une prothèse telle que <a href="https://github.com/zloirock/core-js#ecmascript-6-symbols" hreflang="en" title="corejs - GitHub">core.js</a>. Étant donné que les symboles ne ressemblent pas entièrement à quelque chose d’existant en JS, cette prothèse ne sera pas parfaite. Lisez-bien <a href="https://github.com/zloirock/core-js#caveats-when-using-symbol-polyfill" hreflang="en" title="corejs - Mises en garde - GitHub">les mises-en-garde</a>.</p>
<p>La semaine prochaine nous publierons <em>deux</em> billets. D’abord, nous nous intéresserons à des fonctionnalités très attendues qui arrivent enfin en JavaScript avec ES6. Nous démarrerons avec deux fonctionnalités qui datent presque de l’aube de la programmation et nous enchaînerons avec deux autres fonctionnalités, très similaires, mais qui ont été améliorées pour les objets éphémères et les références faibles. Rejoignez-nous la semaine prochaine pour examiner les collections ES6 en détails.</p>
<p>Restez également dans les parages pour un billet supplémentaire de Gastòn Silva sur un sujet qui ne concerne aucune fonctionnalité ES6, mais qui pourrait bien vous fournir le petit coup de pouce nécessaire pour commencer à utiliser ES6 dans vos projets. À bientôt !</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : utiliser ES6 dès aujourd'hui avec Babel et Broccoliurn:md5:06f6617a4a8bc21f2dd13edfaf97ec322015-06-21T19:30:00+02:002015-06-21T22:38:14+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/06/21/ES6-en-details-%3A-les-symboles" hreflang="fr" title="Les symboles - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/06/es6-in-depth-babel-and-broccoli/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<hr /> <p><em><a href="https://tech.mozfr.org/tag/ES6%20en%20d%C3%A9tails">ES6 en détails</a> 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é).</em></p>
<p>ES6 est bien là, certains parlent déjà d’ES7, de ce que le futur nous réserve comme fonctionnalités brillantes et standardisées. En tant que développeurs web, nous nous demandons comment nous pouvons exploiter tout ça. À plusieurs reprises, dans les billets précédents de cette série, je vous ai encouragés à développer dès aujourd’hui en utilisant ES6 avec quelques outils intéressants. Je me souviens notamment avoir écrit :</p>
<p><q>Si vous souhaitez utiliser cette fonctionnalité, vous pouvez utiliser <a href="https://babeljs.io/" hreflang="en" title="BabelJS">Babel</a> ou <a href="https://github.com/google/traceur-compiler" title="Traceur - GitHub">Traceur</a>, de Google, pour traduire votre code ES6 en du code ES5, utilisable dès maintenant sur le Web.</q></p>
<p>Aujourd’hui, nous allons vous montrer comment utiliser ces outils, étape par étape. Ceux-ci sont appelés des transpileurs (<em>transpilers</em> en anglais). On les appelle parfois des <a href="https://fr.wikipedia.org/wiki/Compilateur_source_%C3%A0_source" title="Compilateur source à source - Wikipédia">compilateurs source à source</a>, c’est-à-dire un compilatur qui traduit un programme d’un certain langage vers un autre langage pourvu que ces langages aient des niveaux d’abstraction comparables. Les transpileurs nous permettent d’écrire du code ES6 qui pourra être exécuté sans problème dans chaque navigateur.</p>
<h2>Faire du vieux avec du neuf</h2>
<p>Un transpileur est très simple à utiliser. Il s’explique en seulement deux étapes :</p>
<p>1. On écrit du code avec la syntaxe ES6</p>
<pre>
let q = 99;
let maVariable = `${q} bouteilles de lait, ce qui fait ${q} x 0.50€`;
</pre>
<p>2. On utilise le code qu’on vient d’écrire comme entrée pour le transpileur qui le traitera et produira le code suivant en sortie :</p>
<pre>
"use strict";
var q = 99;
var maVariable = "" + q + " bouteilles de lait, ce qui fait " + q + " x 0.50€"
</pre>
<p>Et ce code là, c’est du bon vieux JavaScript qui peut être utilisé dans n’importe quel navigateur.</p>
<p>Le fonctionnement interne d’un transpileur est très complexe et ce n’est pas l’objet de cet article que d’expliquer en détails comment il fonctionne. De la même façon qu’on peut conduire une voiture sans savoir comment les pièces d’un moteur sont imbriquées, aujourd’hui, nous considèrerons simplement le transpileur comme une boîte noire qui peut traiter notre code.</p>
<h2>Babel, à l’attaque !</h2>
<p>Il existe différentes façons d’utiliser Babel dans un projet. Il y a l’outil en ligne de commande qui peut être utilisé avec des commandes comme :</p>
<pre>
babel script.js --out-file script-compiled.js
</pre>
<p>Il y a aussi une version pour les navigateurs qui est disponible : vous pouvez inclure Babel comme n’importe quelle autre bibliothèque JS. Cela fonctionne en plaçant votre code ES6 dans des éléments <code>script</code> dont le type est <code>"text/babel"</code>.</p>
<pre>
<script src="https://tech.mozfr.org/post/2015/06/21/node_modules/babel-core/browser.js"></script>
<script type="text/babel">
// Votre code ES6
</script>
</pre>
<p>Ces méthodes ne conviennent plus lorsque la base de code grandit et que le code se retrouve partagé entre plusieurs fichiers dans différents dossiers. À un moment, vous devrez utiliser un outil de <em>build</em> (NdT : on ne parle pas ici d’un système de compilation qui produirait un code machine de bas niveau) et d’une méthode pour intégrer Babel dans cet outil et les étapes qui le composent.</p>
<p>Dans les sections qui suivent, nous intègrerons Babel avec <a href="http://broccolijs.com/" title="BroccoliJS">Broccoli.js</a> qui est un outil de <em>build</em>. Nous utiliserons ensuite plusieurs exemples et quelques lignes d’ES6 pour démarrer. Si vous rencontrez un problème, vous pourrez réutiliser le code source présent dans ce dépôt : <a href="https://github.com/givanse/broccoli-babel-examples" title="givanse - GitHub">broccoli-babel-examples</a>. Au sein de ce dépôt, vous trouverez trois projets utilisés pour les exemples :</p>
<ol>
<li>es6-fruits</li>
<li>es6-website</li>
<li>es6-modules</li>
</ol>
<p>Chacun de ces exemples est construit à partir du précédent. Nous commencerons avec le strict minimum et nous progresserons vers une solution générique pouvant être utilisée comme point de départ pour un projet ambitieux. Dans ce billet, nous aborderons les deux premiers exemples en détails. Une fois que nous aurons fini avec eux, vous serez en mesure de lire et de comprendre le code du troisième exemple.</p>
<p>Si vous vous dites : « je vais attendre que les navigateurs supportent ces nouvelles fonctionnalités, ce sera plus simple », vous vous retrouverez derrière les autres. Une conformité totale prend du temps pour s’installer dans les différents navigateurs, si elle arrive un jour. Les transpileurs font partie du paysage et vont y rester, il est prévu d’avoir des standards ECMAScript chaque année. On aura donc de nouveaux standards publiés plus souvent qu’on aura des navigateurs homogènes. Montez dans le train et tirez parti des nouvelles fonctionnalités dès maintenant.</p>
<h2>Notre premier projet avec Broccoli et Babel</h2>
<p>Broccoli est un outil conçu pour construire des projets le plus rapidement possible. Entre autres choses, vous pouvez minifier et compresser vos fichiers grâce à <a href="https://www.npmjs.com/browse/keyword/broccoli-plugin" title="npm Broccoli plugins">des extensions Broccoli</a>. Cela vous évite d’avoir à gérer des fichiers, répertoires et d’avoir à exécuter des commandes à chaque fois que vous apportez une modification à un projet. Il faut voir Broccoli comme :</p>
<p><q> un outil comparable aux outils Rails qui fonctionnent par étapes (pipeline) mais qui fonctionne avec Node et qui est agnostique par rapport au serveur utilisé.</q></p>
<h2>Mise en place du projet</h2>
<h3>Node</h3>
<p>Comme vous avez pu le deviner, vous aurez besoin d’installer <a href="https://nodejs.org/" title="Node JS">Node 0.11 ou une version ultérieure</a>.</p>
<p>Si vous utilisez un système de type Unix, évitez d’installer node depuis le gestionnaire de paquets (<em>apt</em>,<em>yum</em>, etc.). Cela vous évitera d’avoir à utiliser les privilèges root lors de l’installation. Il est préférable d’installer les binaires manuellement grâce au lien précédent, avec votre utilisateur courant. Pour mieux comprendre pourquoi il n’est pas recommandé d’utiliser root et node, vous pouvez lire <a href="http://givan.se/do-not-sudo-npm/">Do not sudo npm (en anglais)</a>. Dans cet article, vous trouverez également <a href="http://givan.se/do-not-sudo-npm/#install-npm-properly">d’autres méthodes alternatives pour l’installation</a>.</p>
<h3>Broccoli</h3>
<p>Pour mettre en place notre projet Broccoli, commençons par :</p>
<pre>
mkdir es6-fruits
cd es6-fruits
npm init
# On crée un fichier vide nommé Brocfile.js
touch Brocfile.js
</pre>
<p>Ensuite, on installe <code>broccoli</code> et <code>broccoli-cli</code></p>
<pre>
# la bibliothèque broccoli
npm install --save-dev broccoli
# l'outil pour la ligne de commande
npm install -g broccoli-cli
</pre>
<h2>Écrivons un peu d’ES6</h2>
<p>Tout d’abord, on crée un dossier <code>src</code> et on y place un fichier <code>fruit.js</code>.</p>
<pre>
mkdir src
vim src/fruits.js
</pre>
<p>Dans ce fichier, on écrit un petit script qui utilise une syntaxe ES6.</p>
<pre>
let fruits = [
{id: 100, nom: 'fraise'},
{id: 101, nom: 'raison'},
{id: 102, nom: 'pêche'}
];
for (let fruit of fruits) {
let message = `ID : ${fruit.id} nom : ${fruit.nom}`;
console.log(message);
}
console.log(`Total de la liste : ${fruits.length}`);
</pre>
<p>Ce fragment de code ci-dessus utilise trois fonctionnalités ES6 :</p>
<ol>
<li><code>let</code> pour utiliser des déclarations limitées à la portée (ça fera l’objet d’un prochain billet)</li>
<li><a href="https://tech.mozfr.org/post/2015/05/20/ES6-en-details-%3A-les-iterateurs-et-la-boucle-for-of" title=" Les itérateurs et la boucle for-of - tech.mozfr.org">une boucle for-of</a></li>
<li><a href="https://tech.mozfr.org/post/2015/05/27/ES6-en-details-%3A-les-gabarits-de-chaines-de-caracteres" title=" Les gabarits de chaînes - tech.mozfr.org">des gabarits de chaîne de caractères</a></li>
</ol>
<p>Maintenant, sauvegardez le fichier et essayez de le lancer</p>
<pre>
node src/fruits.js
</pre>
<p>Ça ne fonctionne pas encore tout à fait mais nous allons le rendre exécutable pour Node ainsi que pour n’importe quel navigateur.</p>
<pre>
let fruits = [
^^^^^^
SyntaxError: Unexpected identifier
</pre>
<h2>Un peu d’efforts, on va transpiler</h2>
<p>Nous allons utiliser Broccoli pour charger notre code et le faire passer par Babel. Pour cela, nous éditons le fichier <code>Brocfils.js</code> et nous ajoutons ce code :</p>
<pre>
// on importe l'extension babel
var babel = require('broccoli-babel-transpiler');
// on récupère la source et on la transpile en une étape
fruits = babel('src'); // src/*.js
module.exports = fruits;
</pre>
<p>Remarquons qu’il faut ici <code>broccoli-babel-transpiler</code> qui est un plugin Babel fonctionnant avec la bibliothèque Babel. Il faut l’installer avec :</p>
<p><code>npm install --save-dev broccoli-babel-transpiler</code></p>
<p>Nous pouvons ensuite construire notre projet et exécuter notre script avec :</p>
<pre>
broccoli build dist # on compile
node dist/fruits.js # on exécute le code ES5
</pre>
<p>Ce qui devrait produire quelque chose comme :</p>
<pre>
ID : 100 nom: fraise
ID : 101 nom: raison
ID : 102 nom: pêche
Totale de la liste : 3
</pre>
<p>Et voilà, aussi simple que ça ! Vous pouvez ouvrir le fichier <code>dist/fruits.js</code> pour voir le code transpilé. Une des fonctionnalités appréciables du transpileur Babel est qu’il produit toujours un code lisible.</p>
<h2>Écrivons du code ES6 pour un site web</h2>
<p>Dans notre second exemple, nous allons monter d’une niveau. Tout d’abord, quittez le dossier <code>es6-fruits</code> et créez un nouveau répertoire <code>es6-website</code> avec les étapes que nous avons vues au paragraphe <strong>Mise en place du projet</strong> ci-avant.</p>
<p>Dans le dossier <code>src</code>, nous créerons trois fichiers :</p>
<ul><li><code>src/index.html</code>
<p><pre>
<!DOCTYPE html>
<html>
<head>
<title>Aujourd'hui : ES6</title>
</head>
<style>
body {
border: 2px solid #9a9a9a;
border-radius: 10px;
padding: 6px;
font-family: monospace;
text-align: center;
}
.color {
padding: 1rem;
color: #fff;
}
</style>
<body>
<h1>Aujourd'hui : ES6</h1>
<div id="info"></div>
<hr>
<div id="content"></div>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://tech.mozfr.org/post/2015/06/21/js/mon-app.js"></script>
</body>
</html>
</pre></p>
</li>
<li><code>src/print-info.js</code>
<p><pre>
function printInfo() {
$('#info')
.append('<p>Un exemple de site web minimaliste avec ' +
'Broccoli et Babel</p>');
}
$(printInfo);
</pre></p>
</li>
<li><code>src/print-colors.js</code>
<p><pre>
// Un générateur ES6
function* hexRange(start, stop, step) {
for (var i = start; i < stop; i += step) {
yield i;
}
}
function printColors() {
var content$ = $('#content');
// un exemple inventé de toutes pièces
for ( var hex of hexRange(900, 999, 10) ) {
var newDiv = $('<div>')
.attr('class', 'color')
.css({ 'background-color': `#${hex}` })
.append(`hex code: #${hex}`);
content$.append(newDiv);
}
}
$(printColors);
</pre></p>
</li>
</ul>
<p>Peut-être avez-vous remarqué ce « <code>function* hexRange</code> » ? Oui, c’est bien <a href="https://tech.mozfr.org/post/2015/05/23/ES6-en-details-%3A-les-generateurs" title="Les générateurs - tech.mozfr.org">un générateur ES6</a>. À l’heure actuelle, cette fonctionnalité n’est pas supportée par tous les navigateurs. Afin de pouvoir l’utiliser, nous aurons besoin d’utiliser une prothèse (ou <em>polyfill</em>). Ce sera Babel qui la fournira et nous pourrons la mettre en œuvre très rapidement.</p>
<p>La prochaine étape consiste à fusionner ces différents fichiers JS et à les utiliser dans un site web. La partie la plus compliquée sera d’écrire notre fichier <code>Brocfile</code>. Pour cet exemple, nous installerons 4 plugins :</p>
<p><code>npm install --save-dev broccoli-babel-transpiler</code></p>
<p><code>npm install --save-dev broccoli-funnel</code></p>
<p><code>npm install --save-dev broccoli-concat</code></p>
<p><code>npm install --save-dev broccoli-merge-trees</code></p>
<p>Utilisons ces plugins et modifions notre fichier <code>Brocfile</code> :</p>
<pre>
// Le transpileur Babel
var babel = require('broccoli-babel-transpiler');
// filtrer les arborescences (un sous-ensemble de fichiers)
var funnel = require('broccoli-funnel');
// concaténer des arborescences
var concat = require('broccoli-concat');
// fusionner des arborescences
var mergeTrees = require('broccoli-merge-trees');
// On transpile les fichiers source
var appJs = babel('src');
// On récupère la prothèse fournie par la bibliothèque
var babelPath = require.resolve('broccoli-babel-transpiler');
babelPath = babelPath.replace(/\/index.js$/, '');
babelPath += '/node_modules/babel-core';
var browserPolyfill = funnel(babelPath, {
files: ['browser-polyfill.js']
});
// On ajoute la prothèse Babel à l'arborescence des fichiers
// transpilés
appJs = mergeTrees([browserPolyfill, appJs]);
// On concatène les fichiers JS en un seul fichier
appJs = concat(appJs, {
// on définit l'ordre de concaténation
inputFiles: ['browser-polyfill.js', '**/*.js'],
outputFile: '/js/mon-app.js'
});
// On récupère le fichier index
var index = funnel('src', {files: ['index.html']});
// On récupère toutes les arborescences pour les
// exporter et crée une seule arborescence finale
module.exports = mergeTrees([index, appJs]);
</pre>
<p>C’est le moment de construire et d’exécuter notre code :</p>
<pre>
broccoli build dist
</pre>
<p>Cette fois, vous devriez voir la structure suivante sous le dossier <code>dist</code> :</p>
<pre>
$> tree dist/
dist/
├── index.html
└── js
└── mon-app.js
</pre>
<p>Et voici un site web statique qu’on peut servir avec n’importe quel serveur pour vérifier que le code fonctionne. Par exemple :</p>
<pre>
cd dist
python -m SimpleHTTPServer
# visitez http://localhost:8000
</pre>
<p>Vous devriez voir quelque chose qui ressemble à :
<img src="https://tech.mozfr.org/dotclear/public/.screenshot_m.png" alt="screenshot.png" style="display:block; margin:0 auto;" title="screenshot.png, juin 2015" /></p>
<h2>La suite avec Babel et Broccoli</h2>
<p>Ce deuxième exemple vous montre ce qu’il est possible de faire avec Babel. Il pourra éventuellement vous suffire pour quelques projets. Si vous voulez aller plus loin avec ES6, Babel et Broccoli, n’hésitez pas à forker ce dépôt : <a href="https://github.com/jayphelps/broccoli-babel-boilerplate" title=" jayhelps - GitHub">broccoli-babel-boilerplate</a>. C’est aussi un exemple utilisant Broccoli et Babel. Ce depôt vous permettra de manipuler les modules, les imports et les tests unitaires.</p>
<p>Vous pouvez essayer un exemple de cette configuration ici : <a href="https://github.com/givanse/broccoli-babel-examples/tree/master/es6-modules" title="Exemple - GitHub">es6-modules</a>. Toute la magie de l’exemple se trouve dans le fichier <code>Brocfile</code> et vous verrez que ça ressemble beaucoup à ce qu’on a fait.</p>
<hr />
<p><em>Comme vous pouvez le voir, Babel et Broccoli permettent d’utiliser les fonctionnalités d’ES6 dès aujourd’hui de façon très pratique. Merci à Gastón I. Silva pour le billet de cette semaine !</em></p>
<p><em>La semaine prochaine, ce sera le début d’une pause de deux semaines pour la série ES6 en détails (NdT : le prochain article sur tech.mozfr.org arrive la semaine prochaine :)). Nous avons déjà exploré de nombreuses nouveautés mais les fonctionnalités les plus puissantes d’ES6 restent à découvrir…</em>
<em>Rendez-vous dans deux semaines pour la suite et un nouveau billet.</em></p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les fonctions fléchéesurn:md5:eac890cc4d09c9e272dd84d2fc849b872015-06-14T20:26:00+02:002015-08-14T07:53:31+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/06/05/ES6-en-details-%3A-la-decomposition" hreflang="fr" title="La décomposition - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/06/es6-in-depth-arrow-functions/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à Marine, Mentalo, Benjamin, Banban et goofy pour la traduction et la relecture !</em></p>
<hr /> <p><em>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é).</em></p>
<p>Les flèches font partie de JavaScript depuis longtemps. Les premiers tutoriels JavaScript conseillaient de placer les scripts dans des commentaires HTML. Cela empêchait les navigateurs qui ne supportaient pas JavaScript d’afficher le code comme du texte. Cela s’écrivait ainsi :</p>
<pre>
<script language="JavaScript">
<!--
document.bgColor = "brown"; // red
// -->
</script>
</pre>
<p>Les anciens navigateurs y voient uniquement deux balises non supportées et un commentaire, seuls les plus récents reconnaissent du code JavaScript.</p>
<p>Pour supporter cette vieille « bidouille », le moteur JavaScript de votre navigateur traite donc les caractères <code><!--</code> comme un commentaire de ligne. Cela fait partie du langage depuis longtemps, et ça fonctionne toujours aujourd’hui, pas uniquement pour la balise <code><script></code> mais aussi pour tout le code JS. Cela fonctionne même dans Node.</p>
<p>Pour la première fois, <a href="http://people.mozilla.org/~jorendorff/es6-draft.html#sec-html-like-comments" hreflang="en" title="Draft ES6 - Jason Orendorff">ce type de commentaire est standardisé avec ES6</a>. Mais nous ne sommes pas ici pour parler de cette flèche-là.</p>
<p>La flèche <code>--></code> indique également un commentaire sur une ligne. Ce qui est étrange, c’est qu’en HTML, c’est ce qui précède cette flèche qui est un commentaire, alors qu’en JS, c’est ce qui suit le <code>--></code> qui est considéré comme un commentaire.</p>
<p>Encore plus étrange : cette flèche indique un commentaire seulement si elle est au début d’une ligne. En effet, dans d’autres contextes, <code>--></code> peut être compris comme un opérateur de JavaScript, l’opérateur « va jusqu’à » !</p>
<pre>
function countdown(n) {
while (n --> 0) // "n va jusqu'à zéro"
alert(n);
décollage();
}
</pre>
<p><a href="http://codepen.io/anon/pen/oXZaBY?editors=001" hreflang="en" title="Codepen">Ce code fonctionne vraiment</a>. La boucle itère jusqu’à ce que <code>n</code> vaille 0. Ce n’est pas une nouvelle fonctionnalité d’ES6 mais une combinaison de fonctionnalités classiques, dans laquelle une petite erreur s’est glissée. Avez-vous compris ce qui se passe ? Comme souvent, la réponse à cette énigme <a href="http://stackoverflow.com/questions/1642028/what-is-the-name-of-the-operator" hreflang="en" title="Question 1642028 - StackOverflow">se trouve sur Stack Overflow</a>.</p>
<p>Bien sûr, il y a aussi l’opérateur d’infériorité <code><=</code>. Vous trouverez peut-être d’autres flèches cachées dans votre code JS. Faisons les comptes… Une flèche semble manquer à l’appel.</p>
<table style="margin-left: auto; margin-right: auto;">
<tbody>
<tr>
<td><code><!--</code></td>
<td>commentaire sur une ligne</td>
</tr>
<tr>
<td><code>--></code></td>
<td>l’opérateur « va jusqu’à »</td>
</tr>
<tr>
<td><code><=</code></td>
<td>inférieur ou égal à</td>
</tr>
<tr>
<td><code>=></code></td>
<td>???</td>
</tr>
</tbody>
</table>
<p>Qu’est-il arrivé au => ? C’est ce que nous allons voir aujourd’hui.</p>
<p>Commençons par parler un peu des fonctions.</p>
<h2>Les expressions de fonctions sont partout</h2>
<p>Ce qui est bien avec JavaScript, c’est que vous pouvez écrire une fonction en plein milieu du code dès que vous en avez besoin.</p>
<p>Par exemple, supposons que vous vouliez dire au navigateur quoi faire quand l’utilisateur clique sur un bouton spécifique. Vous commencez à écrire :</p>
<pre>
$("#confetti-btn").click(
</pre>
<p>La méthode <code>.click()</code> de jQuery prend un argument : une fonction. Aucun problème, vous pouvez l’écrire ici :</p>
<pre>
$("#confetti-btn").click(function (event) {
jouerTrompette();
lancerConfettis();
});
</pre>
<p>Écrire ce genre de code nous semble naturel. Il peut donc paraître étrange de rappeler qu’avant que ce type de programmation ait été popularisé avec JavaScript, <em>de nombreux langages n’avaient pas cette fonctionnalité</em>. Bien sûr, dès 1958, Lisp avait des expressions de fonctions, aussi appelées <em>fonctions lambda</em>. Toutefois, C++, Python, C#, et Java ont vécu des années sans elles.</p>
<p>Ce n’est plus le cas aujourd’hui. Ces quatre langages ont désormais des fonctions lambdas. Les langages les plus récents utilisent tous le concept des lambdas. Un grand merci à JavaScript pour cela. Merci également aux premiers développeurs JavaScript qui ont écrit sans crainte des librairies dépendant fortement des lambdas, ce qui a mené à l’adoption générale des lambdas.</p>
<p>Malheureusement, de tous les langages dont j’ai parlé, JavaScript est celui qui possède la syntaxe la moins concise pour les fonctions lambdas.</p>
<pre>
//une fonction très simple écrite en 6 langages
function (a) { return a > 0; } // JS
[](int a) { return a > 0; } // C++
(lambda (a) (> a 0)) ;; Lisp
lambda a: a > 0 # Python
a => a > 0 // C#
a -> a > 0 // Java
</pre>
<h2>Une nouvelle flèche à votre carquois</h2>
<p>ES6 introduit une nouvelle syntaxe pour écrire des fonctions.</p>
<pre>
// ES5
var selected = allJobs.filter(function (job) {
return job.isSelected();
});
// ES6
var selected = allJobs.filter(job => job.isSelected());
</pre>
<p>Lorsque vous avez besoin d’une fonction simple qui utilise un seul argument, vous pouvez utiliser la nouvelle syntaxe fléchée : <code>Identifiant => expression</code>. Il n’y a plus besoin d’écrire <code>function</code>, <code>return</code>, les parenthèses, les accolades, et le point-virgule.</p>
<p>En ce qui me concerne, j’accueille cette fonctionnalité avec joie vu le nombre de fois où j’ai écrit <code>functoin</code> avant d’avoir à le corriger.</p>
<p>Pour écrire une fonction avec plusieurs arguments (ou aucun argument, ou avec <a href="https://tech.mozfr.org/post/2015/05/31/ES6-en-details-%3A-le(s)-parametre(s)-du-reste-et-les-parametres-par-defaut" hreflang="fr" title="Paramètres du reste - tech.mozfr.org">des paramètres du reste ou avec des valeurs par défaut</a>, ou qui utilise <a href="https://tech.mozfr.org/post/2015/06/05/ES6-en-details-%3A-la-decomposition" hreflang="fr" title="La décomposition - tech.mozfr.org">une décomposition</a>), vous devrez ajouter des parenthèses autour de la liste d’arguments.</p>
<pre>
// ES5
var total = values.reduce(function (a, b) {
return a + b;
}, 0);
// ES6
var total = values.reduce((a, b) => a + b, 0);
</pre>
<p>Je trouve cela plutôt joli.
Les fonctions fléchées fonctionnent aussi bien avec les outils fournis par les bibliothèques telles que <a href="http://underscorejs.org/" hreflang="en" title="Underscore.js">Underscore.js</a> et <a href="https://facebook.github.io/immutable-js/" hreflang="en" title="Immutable.js - Facebook">Immutable</a>. En fait, <a href="https://facebook.github.io/immutable-js/docs/#/" hreflang="en" title="Documentation Immutable.js - Facebook">les exemples de la documentation d’Immutable</a> sont tous écrits en ES6, la plupart d’entre eux utilisent déjà les fonctions fléchées.</p>
<p>Que se passe-t-il avec des scénarios moins « fonctionnels » ? Les fonctions fléchées peuvent contenir un bloc d’instructions plutôt qu’une seule expression. Reprenons cet exemple :</p>
<pre>
// ES5
$("#confetti-btn").click(function (event) {
jouerTrompette();
lancerConfettis();
});
</pre>
<p>Avec ES6, ça ressemble à :</p>
<pre>
// ES6
$("#confetti-btn").click(event => {
jouerTrompette();
lancerConfettis();
});
</pre>
<p>Ici, ça n’est qu’une amélioration mineure. En revanche, l’effet sur du code utilisant des promesses (<em><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise" hreflang="fr" title="Promise - MDN">Promises</a></em>) peut être significatif : l’empilement des lignes avec <code> }).then(function (result) { </code> sera drastiquement réduit.
Notez que la fonction fléchée ne renvoie pas automatiquement une valeur. Pour cela, il faut utiliser l’instruction <code>return</code>.
Attention, lorsqu’on manipule les fonctions fléchées pour créer des objets avec des littéraux, il faut toujours entourer l’objet de parenthèses :</p>
<pre>
// crée un nouvel objet jouets vide pour chaque chiot
var jouets = chiots.map(chiot => {}); // BUG !
var jouets = chiots.map(chiot => ({})); // ok
</pre>
<p>En effet, malheureusement, un objet vide <code>{}</code> et un bloc vide <code>{}</code> se ressemblent trait pour trait.</p>
<p>Avec ES6, la règle est la suivante : une accolade <code>{</code> suivant immédiatement une flèche est toujours traitée comme le début d’un bloc, jamais comme le début d’un objet. Le code <code>chiot => {}</code> est donc silencieusement interprété comme une fonction fléchée qui ne fait rien et qui renvoie <code>undefined</code>.</p>
<p>Encore plus déroutant, un littéral objet comme <code>{clé: valeur}</code> ressemble à un bloc contenant une déclaration étiquetée, du moins c’est de cette façon que le comprend le moteur JavaScript. Heureusement <code>{</code> est le seul caractère ambigu, rappelez-vous qu’il faut bien entourer le littéral objet avec des parenthèses.</p>
<h2>Encore <code>this</code> ?</h2>
<p>Le comportement entre les fonctions ordinaires et les fonctions fléchées est légèrement différent. <strong>Les fonctions fléchées n’ont pas leur propre valeur de <code>this</code></strong>. La valeur de <code>this</code> à l’intérieur d’une fonction fléchée est toujours héritée depuis la portée englobante.</p>
<p>Avant de voir ce que ça donne en pratique, prenons un peu de recul.
Comment fonctionne <code>this</code> en JavaScript ? D’où vient sa valeur ? <a href="http://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work" hreflang="en" title="this - StackOverflow">Il n’y a pas de réponse simple</a>. Si cela vous semble simple, c’est parce que vous l’utilisez depuis longtemps !</p>
<p>Nous nous posons souvent cette question car les fonctions définies avec <code>function</code> reçoivent automatiquement une valeur pour <code>this</code>, qu’elles le veuillent ou non. N’avez-vous jamais écrit ceci ?</p>
<pre>
{
...
addAll: function addAll(pieces) {
var self = this;
_.each(pieces, function (piece) {
self.add(piece);
});
},
...
}
</pre>
<p>Ici, vous auriez voulu simplement écrire <code>this.add(piece)</code> dans la fonction interne. Malheureusement, la fonction interne n’hérite pas cette valeur de la fonction externe. À l’intérieur de la fonction interne, ce sera <code>window</code> ou <code>undefined</code>. La variable temporaire <code>self</code> sert à introduire la valeur externe de <code>this</code> dans la fonction interne (une autre solution est l’utilisation de <code>.bind(this)</code> sur la fonction interne ; aucune des deux n’est particulièrement jolie).</p>
<p>Avec ES6, cette bidouille ne sera plus nécessaire si vous respectez les règles suivantes :</p>
<ul>
<li>Utilisez les fonctions non-fléchées pour les méthodes qui seront appelées par la syntaxe <code>objet.méthode()</code>. Ce sont les fonctions qui recevront une valeur significative de this via leur appelant ;</li>
<li>Utilisez les fonctions fléchées pour tout le reste.</li>
</ul>
<pre>
// ES6
{
...
addAll: function addAll(pieces) {
_.each(pieces, piece => this.add(piece));
},
...
}
</pre>
<p>Avec ES6, notez que la méthode <code>addAll</code> reçoit <code>this</code> à partir de sa fonction appelante. La fonction interne est une fonction fléchée, elle hérite donc du <code>this</code> contenu dans la portée englobante.</p>
<p>ES6 fournit aussi un raccourci bonus pour écrire des méthodes au sein des littéraux objets ! Le code précédent peut donc encore être simplifié :</p>
<pre>
// syntaxe ES6
{
...
addAll(pieces) {
_.each(pieces, piece => this.add(piece));
},
...
}
</pre>
<p>Entre les méthodes et les fonctions fléchées, je n’écrirai plus jamais <code>function</code>. Enfin !</p>
<p>Il y a encore une différence mineure entre une fonction fléchée et une fonction « non fléchée » : la fonction fléchée ne reçoit pas d’objet <code>arguments</code> non plus. Bien sûr, avec ES6, il vaut mieux utiliser un paramètre du reste ou une valeur par défaut.</p>
<h2>Utiliser les flèches pour percer la noirceur de l’informatique</h2>
<p>Nous avons abordé de nombreux aspects pratiques pour les fonctions fléchées. Il existe un autre cas dont j’aimerais parler : les fonctions fléchées ES6 peuvent être un outil d’apprentissage pour découvrir les profondeurs de l’informatique et du calcul. À vous de décider si c’est utile ou non.</p>
<p>En 1936, Alonzo Church et Alan Turing développèrent, chacun de leur côté, un puissant modèle mathématique pour effectuer des calculs. Turing appela son modèle « a-machines ». Cependant, dès le début, tout le monde les appela « machines de Turing ». Les travaux de Church concernaient plutôt les fonctions. Son modèle s’appelait le <a href="https://fr.wikipedia.org/wiki/Lambda-calcul" hreflang="fr" title="Lambda calcul - Wikipédia">λ-calcul</a> (λ est la lettre grecque lambda, qui correspond au L moderne). Ces travaux sont à l’origine du mot LAMBDA, utilisé en Lisp pour indiquer des fonctions et c’est pourquoi les expressions de fonction sont aujourd’hui appelées « lambdas ».</p>
<p>Mais qu’est-ce que le λ-calcul ? Qu’est-ce que signifie « modèle de calcul » ?</p>
<p>C’est un concept qu’on peut difficilement expliquer en quelques mots mais essayons : le λ-calcul est un des premiers langages de programmation. Il ne fut pas conçu pour être un langage de programmation (les ordinateurs qui utilisent des programmes sont apparus dix à vingt ans plus tard). C’est une idée purement mathématique, très simple et épurée, qui permet d’exprimer toutes sortes d’opérations qu’on souhaiterait effectuer. Church avait travaillé sur ce modèle afin de pouvoir prouver des théorèmes sur le calcul en général.</p>
<p>Il trouva alors qu’il n’avait besoin que d’une seule chose dans son système : des fonctions.
Essayez de prendre un peu de recul pour réaliser l’importance de cette découverte. Sans les objets, sans les tableaux, sans les nombres, sans les instructions if ou while, sans les points-virgules, sans les affectations, sans les opérateurs logiques ou sans les boucles d’événements, il est possible de reconstruire tous les calculs qu’on peut faire en JavaScript, à partir de zéro, uniquement avec des fonctions.</p>
<p>Voici un exemple d’un « programme » qu’un mathématicien pourrait écrire en utilisant la λ notation de Church.</p>
<p><code>fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))</code></p>
<p>La fonction équivalente, écrite en JavaScript, ressemblerait à :</p>
<pre>
var fix = f => (x => f(v => x(x)(v)))
(x => f(v => x(x)(v)));
</pre>
<p>Cela signifie que JavaScript contient une implémentation fonctionnelle du λ-calcul. <em>Le λ-calcul fait partie de JavaScript.</em></p>
<p>Cet article est trop court pour traiter des découvertes effectuées par Alonzo Church et les chercheurs suivants /successifs autour du λ-calcul et de la façon dont celui-ci s’est intégré discrètement dans la plupart des langages de programmation. Si toutefois l’histoire des origines de l’informatique vous intéresse ou si vous voulez voir comment un langage peut recréer des boucles et des récursions uniquement à partir de fonctions, vous pouvez parfaitement passer une après-midi pluvieuse à plonger dans <a href="https://fr.wikipedia.org/wiki/Codage_unaire" hreflang="fr" title="Nombres de Church - Wikipédia">les nombres de Church</a> et <a href="https://en.wikipedia.org/wiki/Fixed-point_combinator#Strict_fixed_point_combinator" hreflang="en" title="Strict fixed point combinators - Wikipedia">les combinateurs à point fixe</a> pour essayer de les manipuler dans la console ou dans <a href="https://developer.mozilla.org/fr/docs/Outils/Ardoise" hreflang="fr" title="Ardoise - MDN">l’ardoise de développement</a> Firefox. Les fonctions fléchées ES6, associées aux autres atouts d’ES6, font que JavaScript peut raisonnablement se targuer d’être un des meilleurs langages pour explorer le λ-calcul.</p>
<h2>Quand pourrais-je décocher ces flèches ?</h2>
<p>J’ai implémenté les fonctions fléchées ES6 dans Firefox en 2013. Jan de Mooij les a ensuite rendues plus rapides. Merci également à Tooru Fujisawa et ziyunfei pour leurs corrections.</p>
<p>Les fonctions fléchées sont aussi implémentées dans la dernière version de Microsoft Edge. Elles sont aussi disponibles dans <a href="http://babeljs.io/" hreflang="en" title="BabelJS">Babel</a>, <a href="https://github.com/google/traceur-compiler#what-is-traceur" hreflang="en" title="Traceur - GitHub">Traceur</a>, et <a href="http://www.typescriptlang.org/" hreflang="en" title="TypeScript">TypeScript</a> si jamais vous souhaitez les utiliser sur le Web dès aujourd’hui.</p>
<p>Le sujet de notre prochain article sera une des fonctionnalités étranges d’ES6. Nous verrons que <code>typeof x</code> renvoie une nouvelle valeur. Nous nous demanderons dans quels cas un nom n’est pas une chaîne de caractères et nous nous pencherons sur le sens de l’égalité. Soyez prévenus, ce sera étrange…</p>
<p>Rendez-vous donc la semaine prochaine pour explorer les symboles ES6 en détails.</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : la décompositionurn:md5:a7c396ca4848cc28b38148892748f09e2015-06-06T11:11:00+02:002015-08-14T07:53:28+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/05/31/ES6-en-details-%3A-le%28s%29-parametre%28s%29-du-reste-et-les-parametres-par-defaut" hreflang="fr" title="Les paramètres du reste et les paramètres par défaut - tech.mozfr.org">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/05/es6-in-depth-destructuring/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à goofy pour la relecture !</em></p>
<hr /> <p><em>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é).</em></p>
<p><em>Note de l’éditeur : une version du billet d’aujourdhui, écrit par <a href="http://fitzgeraldnick.com/weblog/" hreflang="en" title="Blog de Nick Fitzgerald">Nick Fitzgerald</a>, ingénieur travaillant sur les outils de développement Firefox, avait précédemment été publiée sur son blog : <a href="http://fitzgeraldnick.com/weblog/50/" hreflang="en" title="Destructuring Assignment - Blog de Nick Fitzgerald">L’affectation par décomposition dans ES6</a>.</em></p>
<h2>Qu’est-ce que l’affectation par décomposition ?</h2>
<p>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.</p>
<p>Sans utiliser l’affectation par décomposition, on pourrait accéder aux trois premiers éléments d’un tableau de cette façon :</p>
<pre>
var premier = unTableau[0];
var deuxième = unTableau[1];
var troisième = unTableau[2];
</pre>
<p>Grâce à l’affectation par décomposition, on obtient un code équivalent, beaucoup plus lisible et concis :</p>
<pre>
var [premier, deuxième, troisième] = unTableau;
</pre>
<p>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. <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=694100" hreflang="en" title="Bug 694100 - bugzilla.mozilla.org">Le support de SpiderMonkey pour la décomposition (et ES6 en général) est suivi avec le bogue 694100.</a></p>
<h2>Décomposer les tableaux et les itérables</h2>
<p>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 :</p>
<pre>
[ variable1, variable2, ..., variableN ] = tableau;
</pre>
<p>Cela affectera les éléments respectifs du tableau <code>tableau</code> aux variables <code>variable1</code> à <code>variableN</code>. Si vous souhaitez déclarer vos variables en même temps, vous pouvez ajouter un <code>var</code>, <code>let</code> ou un <code>const</code> devant l’affectation.</p>
<pre>
var [ variable1, variable2, ..., variableN ] = tableau;
let [ variable1, variable2, ..., variableN ] = tableau;
const [ variable1, variable2, ..., variableN ] = tableau;
</pre>
<p>De fait, le terme « variable » est un peu usurpé ici car il est possible d’imbriquer des structures comme on le souhaite :</p>
<pre>
var [toto, [[truc], machin]] = [1, [[2], 3]];
console.log(toto);
// 1
console.log(truc);
// 2
console.log(machin);
// 3
</pre>
<p>De plus, on peut sauter des éléments du tableau qui est décomposé :</p>
<pre>
var [,,troisième] = ["toto", "truc", "machin"];
console.log(troisième);
// "machin"
</pre>
<p>Et il est possible de capturer les éléments en fin de tableau avec un paramètre du reste :</p>
<pre>
var [tête, ...queue] = [1, 2, 3, 4];
console.log(queue);
// [2, 3, 4]
</pre>
<p>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 : <code>undefined</code>.</p>
<pre>
console.log([][0]);
// undefined
var [manquant] = [];
console.log(manquant);
// undefined
</pre>
<p>On notera que l’affectation par décomposition pour un tableau fonctionne également avec n’importe quel itérable :</p>
<pre>
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
</pre>
<h2>Décomposer des objets</h2>
<p>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.</p>
<pre>
var robotA = { nom: "Bender" };
var robotB = { nom: "Flexo" };
var { nom: nomA } = robotA;
var { nom: nomB } = robotB;
console.log(nomA);
// "Bender"
console.log(nomB);
// "Flexo"
</pre>
<p>Cela représente un raccourci syntaxique utile lorsque les noms des propriétés et les noms des variables sont les mêmes :</p>
<pre>
var { toto, truc } = { toto: "lorem", truc: "ipsum" };
console.log(toto);
// "lorem"
console.log(truc);
// "ipsum"
</pre>
<p>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 :</p>
<pre>
var objetCompliqué = {
propTableau: [
"Zapp",
{ second: "Brannigan" }
]
};
var { propTableau: [premier, { second }] } = objetCompliqué;
console.log(premier);
// "Zapp"
console.log(second);
// "Brannigan"
</pre>
<p>Si vous décomposez des propriétés non définies, vous obtiendrez <code>undefined</code> :</p>
<pre>
var { manquante } = {};
console.log(manquante);
// undefined
</pre>
<p>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 <code>let</code>, ni <code>const</code>, ni <code>var</code>) :</p>
<pre>
{ bombe } = { bombe: 10 };
// Erreur de syntaxe
</pre>
<p>Cela se produit car la grammaire JavaScript indique au moteur que toute instruction commençant par un <code>{</code> doit être comprise comme un bloc d’instruction (par exemple, <code>{ console }</code> représente une instruction de bloc valide). La solution est d’entourer toute l’expression avec des parenthèses :</p>
<pre>
({ toto } = {});
// Pas d'erreur
</pre>
<h2>Décomposer des valeurs qui ne sont pas des objets, des tableaux ou des itérables</h2>
<p>Si vous essayez d’utiliser la décomposition sur <code>null</code> ou <code>undefined</code>, vous obtiendrez une exception <code>TypeError</code> :</p>
<pre>
var [bombe] = null;
// TypeError: null has no properties
</pre>
<p>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… <code>undefined</code> :</p>
<pre>
var [wtf] = NaN;
console.log(wtf);
// undefined
</pre>
<p>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, <a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-requireobjectcoercible" hreflang="en" title="Object coercible - Draft ES6 ">la valeur décomposée doit nécessairement être convertible en un objet</a>. La plupart des types peuvent être convertis en un objet mais <code>null</code> et <code>undefined</code> ne peuvent pas être convertis. Lorsqu’on utilise la décomposition d’un tableau, la valeur décomposée doit <a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-getiterator" hreflang="en" title="getiterator - Draft ES6">avoir un itérateur</a> (propriété <code>Symbol.iterator</code>).</p>
<h2>Valeurs par défaut</h2>
<p>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 :</p>
<pre>
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
</pre>
<p>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 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=932080" hreflang="en" title="Bug 932080 - bugzilla.mozilla.org">le bogue 932080</a>.</p>
<h2>Applications pratiques pour la décomposition</h2>
<h3>Définitions des paramètres d’une fonction</h3>
<p>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 :</p>
<pre>
function removeBreakpoint({ url, line, column }) {
// ...
}
</pre>
<p>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.</p>
<h3>Paramètres d’un objet de configuration</h3>
<p>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 <code>ajax</code>, par exemple, utilise un objet de configuration comme second paramètre. Elle pourrait être réécrite comme ceci :</p>
<pre>
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
};
</pre>
<p>Cela permet d’éviter d’avoir à répéter <code>var toto = config.toto || defautPourToto;</code> pour chacune des propriétés de l’objet de configuration</p>
<p>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 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=932080" hreflang="en" title="Bug 932080 - bugzilla.mozilla.org">le bogue 932080</a> pour les informations les plus récentes à ce sujet.</p>
<h3>Décomposition et protocole d’itération ES6</h3>
<p><a href="https://tech.mozfr.org/post/2015/05/20/ES6-en-details-%3A-les-iterateurs-et-la-boucle-for-of" hreflang="fr" title="Les itérateurs et la boucle for-of">ECMAScript 6 définit également un protocole d’itération</a>, que nous avons évoqué précédemment dans cette série d’articles. Lorsque vous parcourez des objets <code>Map</code> (<a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Map" hreflang="fr" title="Map - MDN">un ajout à la bibliothèque native ES6</a>), vous obtenez une série de paires <code>[clé, valeur]</code>. Cette paire peut être décomposée pour accéder facilement à la clé d’une part et à la valeur d’autre part.</p>
<pre>
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) {
// ...
}
</pre>
<h3>Renvoyer plusieurs valeurs</h3>
<p>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 :</p>
<pre>
function renvoyerPlusieursValeurs() {
return [1, 2];
}
var [toto, truc] = renvoyerPlusieursValeurs();
</pre>
<p>Vous pouvez aussi utiliser un objet qui agit comme un conteneur et qui nomme les valeurs renvoyées :</p>
<pre>
function renvoyerPlusieursValeurs() {
return {
toto: 1,
truc: 2
};
}
var { toto, truc } = renvoyerPlusieursValeurs();
</pre>
<p>Ces deux constructions sont beaucoup plus utiles qu’un conteneur temporaire :</p>
<pre>
function renvoyerPlusieursValeurs() {
return {
toto: 1,
truc: 2
};
}
var temp = renvoyerPlusieursValeurs();
var toto = temp.toto;
var truc = temp.truc;
</pre>
<p>Elles sont également mieux que le style qui consiste à passer les valeurs directement à une autre fonction :</p>
<pre>
function renvoyerPlusieursValeurs(k) {
k(1, 2);
}
renvoyerPlusieursValeurs((toto, truc) => ...);
</pre>
<h3>Importer des noms à partir d’un module CommonJS</h3>
<p>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 :</p>
<pre>
const { SourceMapConsumer, SourceNode } = require("source-map");
</pre>
<p>(Si vous utilisez les modules ES6, une syntaxe similaire est disponible avec les déclarations d’import.)</p>
<h2>Conclusion</h2>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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 <a href="http://babeljs.io/" hreflang="en" title="BabelJS">Babel</a> ou de <a href="https://github.com/google/traceur-compiler#what-is-traceur" hreflang="en" title="Traceur - GitHub">Traceur</a>.</p>
<hr />
<p><em>Merci encore à Nick Fitzgerald pour le billet de cette semaine.</em></p>
<p><em>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.</em></p>
<p><em>Rejoignez-nous la semaine prochaine pour explorer les fonctions fléchées ES6 en détails.</em></p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les paramètres du reste et les paramètres par défauturn:md5:0d3b44ced80e0f900a99914b46c61ec52015-06-06T11:10:00+02:002015-06-21T19:49:46+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/05/27/ES6-en-details-%3A-les-gabarits-de-chaines-de-caracteres" hreflang="fr" title="ES6 en détails : les gabarits de chaînes">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/05/es6-in-depth-rest-parameters-and-defaults/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci à Marine et Banban pour la traduction et à goofy pour la relecture !</em></p>
<hr /> <p><em>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é).</em></p>
<p>L’article d’aujourd’hui concerne deux fonctionnalités qui rendent les fonctions JavaScript plus expressives : les paramètres du reste et les paramètres par défaut.</p>
<h2>Les paramètres du reste (<em>rest parameters</em>)</h2>
<p>Lorsqu’on crée une API, on a souvent besoin d’une fonction <em>variadique</em>, c’est-à-dire une fonction qui prend en entrée un nombre variable d’arguments. La méthode <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/concat" hreflang="fr" title="String.prototype.concat() - MDN">String.prototype.concat</a>, par exemple, accepte autant de chaînes qu’on veut. Grâce aux paramètres du reste, ES6 fournit une nouvelle façon d’écrire des fonctions variadiques.</p>
<p>Afin d’illustrer ceci, écrivons une fonction variadique simple, <code>contientTout</code>, qui vérifie si une chaîne contient plusieurs sous-chaînes. On aura par exemple <code>contientTout("banane","b","nan")</code> qui renverra <code>true</code> et <code>contientTout("banane","c","nan")</code> qui renverra <code>false</code>.</p>
<p>Habituellement, on pourrait implémenter cette fonction de cette façon :</p>
<pre>
function contientTout(tasFoin) {
for (var i = 1; i < arguments.length; i++) {
var aiguille = arguments[i];
if (tasFoin.indexOf(aiguille) === -1) {
return false;
}
}
return true;
}
</pre>
<p>Cette implémentation utilise <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions/arguments" hreflang="fr" title="arguments - MDN">l’objet magique</a> <code>arguments</code> qui est un objet semblable à un tableau et qui contient les paramètres passés à la fonction. Ce code fait ce que l’on souhaite mais sa lisibilité n’est pas optimale. La liste des paramètres de la fonction contient un seul élément, <code>tasFoin</code> : il est donc impossible de voir, d’un simple coup d’œil, que la fonction accepte en réalité plusieurs arguments.</p>
<p>Par ailleurs, il faut être prudent et bien débuter l’itération des arguments à 1 et non à 0. En effet, <code>arguments[0]</code> correspond à <code>tasFoin</code>. Si jamais nous voulons ajouter un autre paramètre avant ou après <code>tasFoin</code>, nous devons nous souvenir qu’il faut mettre à jour la boucle for. Les paramètres du reste permettent de résoudre ces deux problèmes.</p>
<p>Voici une deuxième implémentation <code>contientTout</code> qui utilise les fonctionnalités ES6 et les paramètres du reste :</p>
<pre>
function contientTout(tasFoin, ...aiguilles) {
for (var aiguille of aiguilles) {
if (tasFoin.indexOf(aiguille) === -1) {
return false;
}
}
return true;
}
</pre>
<p>Cette version possède le même comportement que la première. Cependant, elle utilise ici la syntaxe spéciale <code>...aiguilles</code>. Voyons comment l’appel de cette fonction se passe avec <code>contientTout("banane","b","nan")</code>. L’argument <code>tasFoin</code> correspond bien au premier argument, ici <code>"banane"</code>. Les points de suspension avant <code>aiguilles</code> indiquent qu’il s’agit des paramètres du reste. Tous les autres paramètres fournis à la fonction sont placés dans un tableau et affectés à la variable <code>aiguilles</code>. Dans notre exemple, <code>aiguilles</code> contient <code>["b","nan"]</code>. L’exécution de la fonction continue ensuite normalement.</p>
<p>Note : nous avons ici utilisé la boucle ES6 <a href="https://tech.mozfr.org/fr/docs/Web/JavaScript/Reference/Instructions/for...of" hreflang="fr" title="for-of - MDN">for-of</a>.</p>
<p>Seul le dernier paramètre d’une fonction peut représenter les paramètres du reste. Dans un appel, les paramètres situés avant celui-ci seront traités de façon habituelle. N’importe quel argument additionnel sera mis dans un tableau et ajouté aux paramètres du reste. S’il n’y en a pas, le paramètre du reste sera un tableau vide, il ne sera jamais <code>undefined</code>.</p>
<h2>Les paramètres par défaut</h2>
<p>Très souvent, il n’est pas nécessaire de passer l’ensemble des paramètres d’une fonction lors d’un appel et on peut avoir des valeurs par défauts raisonnables utilisées quand des paramètres ne sont pas passés. Auparavant, JavaScript a toujours été rigide sur les valeurs par défaut des paramètres : ceux qui n’étaient pas passés à la fonction lors de l’appel valaient inconditionnellement <code>undefined</code>. ES6 introduit une nouvelle syntaxe pour définir des valeurs par défaut personnalisées.</p>
<p>Voici un exemple :</p>
<pre>
function phraseAnimaux(animaux2="tigres", animaux3="ours") {
return `Oh ! Des lions, des ${animaux2} et des ${animaux3} !`;
}
</pre>
<p>Note : cet exemple utilise les gabarits de chaînes de caractères dont on a discuté <a href="https://tech.mozfr.org/post/2015/05/27/ES6-en-details-%3A-les-gabarits-de-chaines-de-caracteres" hreflang="fr" title="Gabarits - tech.mozfr.org">la semaine dernière</a>.</p>
<p>Pour chaque paramètre, la partie située après le <code>=</code> est une expression qui définit la valeur par défaut à utiliser lorsque l’appelant ne passe pas de valeur pour ce paramètre. Ainsi, <code>phraseAnimaux()</code> renvoie “Oh ! Des lions, des tigres et des ours !”, <code>phraseAnimaux("éléphants")</code> renverra “Oh ! Des lions, des éléphants et des ours  !”, <code>phraseAnimaux("éléphants", "baleines")</code> renverra “Oh ! Des lions, des éléphants et des baleines !”.</p>
<p>Voici quelques subtilités liées aux paramètres par défaut :</p>
<ul>
<li><p> À la différence de Python, <b>les expressions des valeurs par défaut sont évaluées lors de l’appel de la fonction</b>, de gauche à droite. Cela signifie également que les expressions peuvent utiliser les valeurs calculées pour les paramètres précédents. Par exemple, on pourrait améliorer la fonction précédente sur les phrases avec les animaux de la façon suivante :</p>
<pre>
function joliePhraseAnimaux(animaux2="tigres",
animaux3 = animaux2 == "ours") ? "phoques" : "ours"){
return `Oh ! Des lions, des ${animaux2} et des ${animaux3} !`;
}
</pre>
<p>Ainsi, <code>joliePhraseAnimaux("ours")</code> renverrait “Oh ! Des lions, des ours et des phoques !”.</p>
<li><p>Si on passe la valeur <code>undefined</code>, cela sera équivalent à ne rien passer du tout. Ainsi, <code>phraseAnimaux(undefined, "licornes")</code> renvoie “Oh ! Des lions, des tigres et des licornes !”.</p></li>
<li><p>Un paramètre sans valeur par défaut vaudra <code>undefined</code> s’il n’est pas passé à la fonction lors de l’appel, on a donc :</p>
<code>function maFonction(a=42, b) {...}</code>
<p>qui est autorisé et qui est équivalent à </p>
<code>function maFonction(a=42, b=undefined) {...}</code>
</ul>
<h2>En finir avec l’objet arguments</h2>
<p>On a vu ici que les paramètres du reste et les paramètres par défaut permettent de remplacer l’objet <code>arguments</code>. Retirer <code>arguments</code> rend généralement le code plus lisible. En plus de porter atteinte à la lisibilité, le comportement de l’objet <code>arguments</code> pose de nombreux problèmes dès lors qu’on souhaite <a href="https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments" hreflang="en" title="Managing arguments - GitHub">optimiser les machines virtuelles JavaScript</a>.</p>
<p>On espère que les paramètres du reste et les paramètres par défaut remplaceront complètement <code>arguments</code>. Pour commencer, l’objet <code>arguments</code> ne peut plus être utilisé lorsqu’une fonction utilise des paramètres du reste ou des paramètres par défaut. Malgré cela, le support pour cet objet ne s’arrêtera pas du jour au lendemain (s’il s’arrête un jour). Cependant, il est préférable d’éviter l’objet <code>arguments</code> et d’utiliser les paramètres par défaut et les paramètres du reste dès que possible.</p>
<h2>Quand puis-je commencer à utiliser cette fonctionnalité ?</h2>
<p>Firefox supporte les paramètres du reste et les valeurs par défaut depuis la version 15.</p>
<p>Malheureusement, aucun autre navigateur ne supporte les paramètres du reste ni les valeurs par défaut pour le moment. V8 a ajouté récemment le <a href="https://code.google.com/p/v8/issues/detail?id=2159" hreflang="en" title="v8 issue 2159 - Google">support expérimental des paramètres du reste</a> et il existe une <em>issue</em> V8 concernant <a href="https://code.google.com/p/v8/issues/detail?id=2160" hreflang="en" title="Issue V8 2160 - Google">l’implémentation des valeurs par défaut</a>. De même, JSC a ouvert <a href="https://bugs.webkit.org/show_bug.cgi?id=38409" hreflang="en" title="Bug default - Webkit">deux</a> <a href="https://bugs.webkit.org/show_bug.cgi?id=38408" hreflang="en" title="Bug rest - Webkit">bogues</a>.</p>
<p>Les compilateurs <a href="http://babeljs.io/" hreflang="en" title="BabelJS">Babel</a> et <a href="https://github.com/google/traceur-compiler#what-is-traceur" hreflang="en" title="Traceur - GitHub">Traceur</a> supportent tous les deux les valeurs par défaut, il est donc possible de les utiliser dès maintenant.</p>
<h2>Conclusion</h2>
<p>Bien qu’ils n’ajoutent pas de nouveau comportement d’un point de vue technique, les paramètres du reste et les valeurs par défaut améliorent la lisibilité du code et rendent les déclarations de fonctions JavaScript plus expressives.</p>
<hr />
<p><em>Note : Merci à Benjamin Peterson pour avoir implémenté ces éléments dans Firefox, pour ses contributions au projet et bien sûr pour l’article de cette semaine.</em></p>
<p><em>La semaine prochaine, nous présenterons un autre élément de ES6 simple, élégant, pratique. Il reprend la syntaxe que vous connaissez déjà pour écrire les tableaux et les objets et la retourne afin de démonter les tableaux et les objets de façon lapidaire. Qu’est-ce que ça veut dire ? Pourquoi voudrait-on démonter un objet ?</em></p>
<p><em>Rejoignez-nous la semaine prochaine pour répondre à cette question : <a href="https://twitter.com/fitzgen" hreflang="en" title="@fitzgen">Nick Fitzgerald</a>, ingénieur chez Mozilla, nous présentera en détails comment ES6 permet de décomposer des objets.</em></p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les gabarits de chaînes de caractèresurn:md5:871110fd614fe3f2239b80e334dbafc82015-05-31T22:15:00+02:002020-12-09T19:18:40+01:00sphinxJavaScriptECMAScript6ES6Hacks<p><em><a href="https://tech.mozfr.org/post/2015/05/23/ES6-en-details-%3A-les-generateurs" hreflang="fr" title="ES en détails : les générateurs">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/05/es6-in-depth-template-strings-2/">ici</a>. Vous pouvez retrouver les différents articles de la série grâce aux mots-clefs.</em></p>
<p><em>Merci aux traducteurs et relecteurs, Marine, Lucas, Benjamin, Goofy et Benoit :) !</em></p>
<hr /> <p><em>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é).</em></p>
<p>La semaine dernière, je vous ai promis un changement de rythme. Après les itérateurs et les générateurs, nous allions nous attaquer à quelque chose de simple. Quelque chose qui ne vous retournerait pas le cerveau. Nous verrons à la fin de cet article comment je tiendrai cette promesse.
Pour l’instant, commençons avec quelque chose de simple.</p>
<h2>Les bases du <em>backtick</em></h2>
<p>ES6 introduit une nouvelle forme de syntaxe pour les littéraux de chaînes de caractères, appelée les gabarits de chaînes (<em>template strings</em>). Ceux-ci ressemblent à des chaînes ordinaires, si ce n’est qu’ils utilisent le caractère de l’accent grave (ou <em>backtick</em>) ` à la place des quotes ou doubles quotes habituelles ' ou “. Dans le cas le plus simple, ce sont juste des chaînes de caractères :</p>
<pre>
context.fillText(`Ceci n'est pas un string.`, x, y);
</pre>
<p>Mais il y a tout de même une raison pour laquelle ceux-ci sont appelés des « gabarits de chaînes de caractères » et pas simplement des « bonnes vieilles chaînes de caractères ennuyeuses qui ne font rien de spécial mais qui utilisent l’accent grave ». Les gabarits de chaînes de caractères font entrer <a href="https://en.wikipedia.org/wiki/String_interpolation" hreflang="en" title="String interpolation - Wikipedia">l’interpolation de chaînes de caractères</a> dans JavaScript. Autrement dit, ils représentent une façon élégante et agréable d’insérer des valeurs JavaScript au sein d’une chaîne.</p>
<p>Il existe des millions de manières pour utiliser cette fonctionnalité, mais celle qui me réchauffe le cœur est la suivante, utilisée pour représenter un message d’erreur :</p>
<pre>
function autoriser(utilisateur, action) {
if (!utilisateur.aLePrivilege(action)) {
throw new Error(
`L'utilisateur ${utilisateur.nom} n'a pas le droit de ${action}.`);
}
}
</pre>
<p>Dans cet exemple, <code>${utilisateur.nom}</code> et <code>${action}</code> sont appelés des substitutions de gabarits. Le moteur JavaScript va insérer les valeurs <code>utilisateur.nom</code> et <code>action</code> dans la chaîne résultante. Cela permettra de générer un message tel que « L’utilisateur jorendorff n’a pas le droit de pratiquer le hockey. » (ce qui s’avère exact, étant donné que je n’ai pas de licence de hockey).</p>
<p>Jusque là, on a simplement une syntaxe légèrement plus élégante que celle de l’opérateur <code>+</code>, avec les détails auxquels on s’attendrait :</p>
<ul>
<li>Le code contenu dans une substitution de gabarit peut être n’importe quelle expression JavaScript : les appels de fonctions, les expressions arithmétiques, et autres sont donc autorisés (si vous le désirez, vous pouvez même embarquer un gabarit de chaîne dans un autre gabarit de chaîne, ce que j’appelle l<em>‘inception de gabarit</em>).</li>
<li>Si une valeur n’est pas une chaîne, celle-ci sera convertie en une chaîne grâce aux règles habituelles. Ainsi, si <code>action</code> est un objet, sa méthode <code>toString()</code> sera invoquée.</li>
<li>Si vous avez besoin d’écrire un accent grave à l’intérieur d’un gabarit de chaîne, il faudra l’échapper avec une barre oblique inversée (ou <em>backslash</em> en anglais) : <code>`\``</code> sera la même chose que <code>"`"</code></li>
<li>De la même manière, si vous avez besoin d’inclure les deux caractères <code>${</code> dans un gabarit de chaîne, je n’ai aucune envie de savoir ce que vous manigancez, mais vous pouvez échapper chacun de ces caractères avec une barre oblique inversée, en écrivant <code>\${</code> ou <code>$\{</code>.</li>
</ul>
<p>Contrairement aux chaînes ordinaires, les gabarits de chaînes peuvent s’étendre sur plusieurs lignes :</p>
<pre>
$("#warning").html(`
<h1>Attention!</h1>
<p>La pratique non autorisée du hockey peut résulter en
une sanction maximale de ${sanctionMaximale} minutes.</p>
`);
</pre>
<p>À l’intérieur d’un gabarit de chaîne, tous les espaces, y compris les sauts de ligne et l’indentation, sont inclus tels quels dans le résultat.</p>
<p>Bien. Au vu de la promesse faite la semaine dernière, je me sens responsable de votre santé mentale. Du coup, voici un avertissement rapide : à partir d’ici, ça va commencer à devenir intense. Vous pouvez arrêter de lire maintenant, éventuellement aller prendre une tasse de café et profiter de votre cerveau encore intact. Sérieusement, il n’y a aucune honte à repartir maintenant. <a href="https://fr.wikipedia.org/wiki/Lopo_Gon%C3%A7alves" hreflang="fr" title="Lopo Gonçalves - Wikipedia">Lopo Gonçalves</a> a-t-il exploré tout l’hémisphère Sud, entièrement, une fois qu’il eut prouvé que les bateaux pouvaient traverser l’équateur sans être détruits par des monstres marins et qu’ils ne tombaient pas par delà le bord de la Terre ? Non. Il est revenu sur ses pas. Il est rentré chez lui et a profité d’un bon repas. Vous aimez les bons repas, n’est-ce-pas ?</p>
<h2>Un accent qui assure grave</h2>
<p>(NdT : on laissera le lecteur apprécier le jeu de mot original « <em>Backtick the future</em> »)</p>
<p>Voici ce que les gabarits de chaîne ne font pas :</p>
<ul>
<li>Ils n’échappent pas automatiquement les caractères spéciaux à votre place. Afin d’éviter toute vulnérabilité liée aux <a href="http://fr.wikipedia.org/wiki/Cross-site_scripting" hreflang="fr" title="XSS - Wikipedia">attaques XSS</a> lors de la manipulation d’éléments dynamiques, il sera nécessaire de continuer à prendre des précautions lorsqu’on utilise des données tierces, de la même façon qu’avec la concaténation classique.</li>
<li>L’interaction avec <a href="http://yuilibrary.com/yui/docs/intl/" hreflang="en" title="YUI Intl">les bibliothèques d’internationalisation</a> (des bibliothèques utilisées pour gérer, dans le code, plusieurs langues à destination des utilisateurs) n’est pas évidente. Les gabarits de chaînes ne prennent pas en charge les formats qui sont spécifiques à certaines langues pour représenter les nombres, les dates et les pluriels.</li>
<li>Ce ne sont pas des remplaçants pour les moteurs de template tels que <a href="https://mustache.github.io/" hreflang="en" title="Mustache - GitHub">Mustache</a> ou <a href="https://mozilla.github.io/nunjucks/" hreflang="en" title="Nunjucks - Mozilla - GitHub">Nunjucks</a>. Les gabarits de chaînes ne disposent pas d’une syntaxe native permettant de réaliser des boucles, par exemple, de construire les lignes d’un tableau HTML à partir d’un tableau JavaScript, ils ne gèrent pas les conditions (bien qu’ici, oui, on puisse utiliser l’inception de gabarit pour ça — enfin selon moi, cela ressemblerait plus à une blague).</li>
</ul>
<p>ES6 ajoute un petit plus aux gabarits de chaînes, qui permet aux développeurs JavaScript et aux concepteurs de bibliothèques de dépasser ces limites. Ce sont ce qu’on appelle les gabarits étiquetés (<em>tagged templates</em>).</p>
<p>La syntaxe des gabarits étiquetés est simple. Ce sont des gabarits de chaînes qui possèdent une <em>étiquette</em> supplémentaire avant le premier accent grave. Dans notre premier exemple, l’étiquette sera <code>SaferHTML</code> et nous allons l’utiliser pour tenter de s’affranchir de la première limite citée avant : échapper automatiquement les caractères spéciaux.</p>
<p>On notera que <code>SaferHTML</code> n’est <em>pas</em> fourni dans la bibliothèque standard d’ES6. Nous allons l’implémenter ci-après.</p>
<pre>
var message =
SaferHTML`<p>${piege.createur} vous a envoyé un piège.</p>`;
</pre>
<p>Ici, l’étiquette est l’identifiant <code>SaferHTML</code>, une étiquette peut également être une propriété, telle que <code>SaferHTML.escape</code> voire l’appel d’une méthode telle que <code>SaferHTML.escape({unicodeControlCharacters:false})]]</code> (pour être tout à fait précis, n’importe quelle expression <a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-left-hand-side-expressions" hreflang="en" title="Left hand side expressions - ES6 Draft">MemberExpression ou CallExpression</a> peut être utilisée comme étiquette).</p>
<p>Nous avons vu que les gabarits de chaîne sans étiquette sont des raccourcis pour la simple concaténation de chaînes. Les gabarits de chaînes étiquetés représentent des raccourcis pour quelque chose de complètement différent : <em>l’appel de fonction</em>.</p>
<p>Le code ci-dessus est équivalent à :</p>
<pre>
var message =
SaferHTML(templateData, piege.createur);
</pre>
<p>où <code>templateData</code> est un tableau immuable, contenant tous les fragments de chaîne du gabarit, créé pour nous par le moteur de JavaScript. Ce tableau comporterait deux éléments, correspondant aux deux fragments du gabarit étiqueté, séparés par une substitution. <code>templateData</code> serait donc similaire à <code>Object.freeze(["<p>","vous a envoyé un piege.</p>"])</code>.</p>
<p>(En fait, il existe une autre propriété présente sur l’objet <code>templateData</code>. Je ne vais pas l’utiliser dans cet article, mais je la mentionnerai ici pour être tout à fait complet : <code>templateData.raw</code> est un autre tableau qui contient l’ensemble des fragments de chaîne présents dans le gabarit étiqueté dont chacun exactement représenté de la façon dont il est présent dans le code source. Ainsi les séquences comme <code>\n</code> resteront intactes et ne seront pas converties en retour à la ligne. L’étiquette standard <code>String.raw</code> utilise ces chaînes-là.)</p>
<p>Ceci offre la liberté nécessaire à la fonction <code>SaferHTML</code> pour interpréter à la fois la chaîne et les éléments de substitution d’une infinité de façons possibles.</p>
<p>Avant de continuer votre lecture, vous voudrez peut-être essayer de trouver ce que <code>SaferHTML</code> est capable de faire et tenter de l’implémenter ? Après tout, ce n’est qu’une simple fonction. Vous pouvez vous servir de la console de développement de Firefox pour tester votre code.
Voici une des réponses possibles (disponible également sur <a href="https://gist.github.com/jorendorff/1a17f69dbfaafa2304f0" hreflang="en" title="Gist exemple - GitHub">ce gist</a>).</p>
<pre>
function SaferHTML(templateData) {
var s = templateData[0];
for (var i = 1; i < arguments.length; i++) {
var arg = String(arguments[i]);
// On échappe les caractères spéciaux pour la substitution.
s += arg.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
// On n'échappe pas les caractères spéciaux dans le gabarit.
s += templateData[i];
}
return s;
}
</pre>
<p>Avec cette définition, le gabarit étiqueté <code>SaferHTML`<p>${piege.createur} vous a envoyé un piège.</p>'</code> devrait s’étendre afin de former la chaîne de caractères <code>"<p>ES6&lt;3er vous a envoyé un piège.</p>"</code>. Vos utilisateurs sont en sécurité, même si un utilisateur doté d’intentions malveillantes, par exemple, <code>PirateGérard <script>alert('xss');</script></code>, leur envoie un piège (si tant est que cela veuille dire quelque chose).</p>
<p>(En parlant des arguments, si la façon dont l’<a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions/arguments" hreflang="fr" title="arguments - MDN">objet arguments</a> est géré vous semble un peu maladroite, passez la semaine prochaine : il y a une <em>autre</em> fonctionnalité ES6 dont je pense qu’elle vous plaira.)</p>
<p>Un seul exemple n’est pas suffisant pour illustrer la flexibilité des gabarits étiquetés. Revisitons la liste précédente des limitations qui s’appliquent aux gabarits de chaînes de caractères afin de voir ce qu’on pourrait faire d’autre.</p>
<ul>
<li><p>Les gabarits de chaînes de caractère n’échappent pas les caractères spéciaux de façon automatique. Mais, comme nous l’avons vu avec les gabarits étiquetés, vous pouvez résoudre ce problème vous-même avec une étiquette.</p></li>
<p>En fait, vous pouvez faire beaucoup mieux que ça.</p>
<p>Du point de vue de la sécurité, ma fonction <code>SaferHTML</code> est plutôt faible. Plusieurs endroits du HTML possèdent des caractères spéciaux différents qui ont besoin d’être échappés de manières différentes ; <code>SaferHTML</code> ne les échappe pas tous. Cependant, en faisant quelques efforts, vous pourriez écrire une fonction <code>SaferHTML</code> beaucoup plus intelligente, qui analyse les fragments de HTML contenus dans les chaînes de <code>templateData</code> afin de discriminer les substitutions qui concernent du code HTML</p>
<ul><li>celles qui sont des attributs d’éléments pour lesquelles on doit échapper <code>'</code> et <code>"</code> ;</li>
<li>celles qui sont des chaînes de requête d’une URL pour lesquelles on doit échapper les caractères spéciaux des URL (plutôt que ceux spéciaux pour le code HTML).</li></ul>
<p>Cette meilleure version pourrait appliquer l’échappement pertinent pour chacune des substitutions.</p>
<p> Est-ce que cela ne semble pas un peu farfelu ? En effet, l’analyse du code HTML est lente. Heureusement, les fragments des chaînes de caractères étiquetées ne changent pas lorsqu’un gabarit est à nouveau évalué. <code>SaferHTML</code> pourrait donc mettre en cache les résultats de l’analyse afin d’accélérer les prochains appels (ce cache pourrait être représenté par un objet <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/WeakMap"><code>WeakMap</code></a>, une autre fonctionnalité de ES6 que nous verrons dans un prochain billet)</p></li>
<li><p>Les gabarits de chaînes de caractères n’ont pas de fonctionnalité d’internationalisation native. Toutefois, avec les étiquettes, il est possible d’en ajouter. <a href="http://jaysoo.ca/2014/03/20/i18n-with-es6-template-strings/">Un billet de blog écrit par Jack Hsu</a> illustre les premières étapes de ce chemin. Voici un exemple pour vous mettre en appétit :<p>
<pre>
i18n`Hello ${name}, you have ${amount}:c(CAD) in your bank account.`
// => Hallo Bob, Sie haben 1.234,56 $CA auf Ihrem Bankkonto.
</pre>
<p>Notez comment, dans cet exemple, le nom et le montant sont des variables JavaScript. On observe aussi un peu d’un autre code : ce <code>:c(CAD)</code>, que Jack place au sein de la chaîne de caractères du gabarit. Les variables JavaScript sont, bien sûr, gérées par le moteur JavaScript, les parties spéciales de la chaîne de caractères sont gérées par l’étiquette <code>i18n de Jack</code>. Si on regarde la documentation, on apprend que <code>:c(CAD)</code> représente une devise : le dollar Canadien.</p>
<p>C’est <em>le</em> rôle des gabarits étiquetés </p>
<li><p>Les gabarits de chaînes de caractère ne sont pas des remplaçants pour les bibliothèque Moustache et Nunjucks, en partie parce qu’ils ne possèdent pas de syntaxe native pour les boucles et les conditions. Avec ce qu’on a vu, on peut réparer ça, non ? Lorsque JS ne fournit pas la fonctionnalité attendue, il suffit d’écrire une étiquette pour cela.<p>
<pre>
// Langage de template hypothétique basé sur les
// gabarits étiquetés ES6.
var libraryHtml = hashTemplate`
<ul>
#for book in ${myBooks}
<li><i>#{book.title}</i> by #{book.author}</li>
#end
</ul>
`;
</pre>
</li>
</ul>
<p>La flexibilité ne s’arrête pas là. Notez que les arguments d’une fonction étiquette ne sont pas automatiquement convertis en chaînes de caractères. Ils peuvent être n’importe quoi. Il en va de même pour la valeur de retour. Il n’est même pas nécessaire que les gabarits étiquetés soient des chaînes de caractères ! Il est possible d’utiliser des étiquettes personnalisées pour créer des expressions rationnelles, des arbres DOM, des images, des promesses représentant des processus asynchrones, des structures de données JS, des shaders GL…</p>
<p><strong>Les gabarits étiquetés permettent aux concepteurs des bibliothèques de créer des langages à part entière pour des domaines spécifiques.</strong> Ces langages ne ressemblent aucunement à JS mais ils peuvent être intégrés à JS sans anicroche et interagir intelligemment avec le reste du langage. À première vue, je ne connais pas de telle fonctionnalité dans les autres langages. Je ne sais pas où cette fonctionnalité va nous emmener. Les possibilités sont formidables.</p>
<h2>Quand puis-je commencer à utiliser cette fonctionnalité ?</h2>
<p>Côté serveur, les modèles de chaînes de caractères ES6 sont supportés sur io.js dès aujourd’hui.</p>
<p>Côté navigateur, Firefox 34 et les versions ultérieures supportent les gabarits de chaînes de caractères. Ils ont été implémentés par Guptha Rajagopa lors de son projet de stage l’été dernier. Les gabarits de chaînes de caractères sont également supportés par Chrome 41 et les versions ultérieures, en revanche ils ne sont pas présents sous IE et Safari. Pour l’instant, vous aurez besoin d’utiliser <a href="http://babeljs.io/" hreflang="en" title="BabelJS">Babel</a> ou <a href="https://github.com/google/traceur-compiler#what-is-traceur" hreflang="en" title="Traceur - GitHub">Traceur</a> si vous souhaitez utiliser des gabarits de chaînes de caractères sur le web. Vous pouvez aussi les utiliser immédiatement avec <a href="http://blogs.msdn.com/b/typescript/archive/2015/01/16/announcing-typescript-1-4.aspx" hreflang="en" title="TypeScript - MSDN">TypeScript</a> !</p>
<h2>Attendez — et à propos de Markdown?</h2>
<p>Hmm ?</p>
<p>Oh… C’est une bonne question.</p>
<p>(Cette section ne concerne pas vraiment JavaScript. Si vous n’utilisez pas <a href="http://daringfireball.net/projects/markdown/basics" hreflang="en" title="Markdown">Markdown</a>, vous pouvez passez outre.)
Avec les gabarits de chaînes de caractères, Markdown et JavaScript utilisent tous les deux le caractère ` pour signifier quelque chose de spécial. Pour le langage Markdown, ce caractère est utilisé comme séparateur afin de représenter du code au milieu d’une ligne de texte.
Cela pose donc un problème, si vous écrivez ceci dans un document écrit en Markdown :</p>
<p><code>Pour afficher un message, il faut écrire `alert(`coucou monde !`)`.</code></p>
<p>Il sera affiché de la façon suivante :</p>
<p>Pour afficher un message, il faut écrire <code>alert(</code>coucou monde !<code>)</code>.</p>
<p>Notez qu’il n’y a pas d’accent grave dans le résultat produit. Les quatre accents graves ont été interprétés en Markdown comme séparateurs pour le code et ont été remplacés par les balises HTML correspondantes.</p>
<p>Pour éviter cela, nous allons nous servir d’une fonctionnalité peu connue, présente en Markdown depuis ses débuts : utiliser plusieurs accents graves pour marquer du code, de cette façon :</p>
<code>Pour afficher un message, écrivez ``alert(`coucou monde !`)``.</code>
<p><a href="https://gist.github.com/jorendorff/d3df45120ef8e4a342e5" hreflang="en" title="Gist - Markdown">Ce gist</a> contient tous les détails et vu qu’il est écrit en Markdown vous pouvez aussi consulter le code source.</p>
<h2>La suite</h2>
<p>La semaine prochaine, nous nous attarderons sur deux fonctionnalités appréciées des programmeurs dans d’autres langages depuis des décennies : l’une qui concerne ceux qui évitent d’avoir à utiliser un argument dès que possible, et l’autre qui concerne ceux pour qui il en faut toujours beaucoup (on parle ici des arguments de fonction). Ces deux fonctionnalités sont vraiment utiles pour chacun d’entre nous.</p>
<p>Nous verrons celles-ci à travers les yeux de la personne qui les a implémentées dans Firefox. La semaine prochaine, ce sera Benjamin Peterson qui sera invité pour présenter, en détails, les paramètres par défaut et les paramètres du reste (<em>rest parameters</em>).</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les générateursurn:md5:979751fa6bd9d5ee51035b16b5d69f312015-05-27T20:40:00+02:002015-06-03T20:59:46+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/05/20/ES6-en-details-%3A-les-iterateurs-et-la-boucle-for-of" hreflang="fr" title="ES en détails : les itérateurs et la boucle for-of">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/05/es6-in-depth-generators/">ici</a>.</em></p>
<p><em>Merci aux traducteurs et relecteurs :) Marine, Mentalo, Benjamin, Ilphrin et Goofy !</em></p>
<hr /> <p><em><a href="https://hacks.mozilla.org/category/es6-in-depth/" hreflang="en" title="ES6 in depth">ES6 en détails</a> 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é).</em></p>
<p>Je suis vraiment enthousiasmé par l’article d’aujourd’hui. Nous allons parler de la fonctionnalité la plus magique d’ES6.</p>
<p>Comment ça, « magique » ? Pour les débutants, cette fonctionnalité est si différente de ce qui existe qu’elle peut paraître un peu surprenante à première vue. D’une certaine façon, elle peut complètement renverser le comportement du langage ! Si ce n’est pas de la magie, je ne sais pas ce que c’est.</p>
<p>Mais ce n’est pas tout : cette fonctionnalité peut repousser les frontières de « l’enfer des <em>callbacks</em> » aux limites du surnaturel.</p>
<p>Est-ce que j’en fais suffisamment assez ? Allons-y et jugez-en par vous-même.</p>
<h2>Introduction aux générateurs ES6</h2>
<p>Que sont les générateurs?</p>
<p>Commençons par en observer un :</p>
<pre>
function* quips(nom) {
yield "bonjour " + nom + " !";
yield "j'espère que vous appréciez ces billets";
if (nom.startsWith("X")) {
yield "Tiens, votre nom commence par un X, " + nom;
}
yield "À la prochaine !";
}
</pre>
<p>Ceci est un morceau de code pour simuler <a href="http://people.mozilla.org/~jorendorff/demos/meow.html" hreflang="en" title="Démo chat">un chat qui parle</a>, probablement un type d’application crucial sur Internet aujourd’hui. (Essayez-la, <a href="http://people.mozilla.org/~jorendorff/demos/meow.html" hreflang="en" title="Démo chat">cliquez sur le lien et jouez avec le chat</a>. Quand vous serez perdu, revenez ici pour quelques explications.)</p>
<p>Cela ressemble à une fonction, n’est-ce pas ? C’est ce qu’on appelle une fonction génératrice (ou générateur) et ça possède beaucoup de liens avec les fonctions. Dès le premier coup d’œil, on peut toutefois observer deux différences :</p>
<ul>
<li>Les fonctions classiques commencent par <code>function</code>. Les fonctions génératrices commencent par <code>function*</code>.</li>
<li>Dans une fonction génératrice, <code>yield</code> est un mot-clé, avec une syntaxe similaire à <code>return</code>. La différence est que, tandis qu’une fonction (même un générateur) ne peut utiliser <code>return</code> qu’une seule fois, un générateur peut utiliser <code>yield</code> plusieurs fois. L’expression <code>yield</code> suspend l’exécution du générateur, qui peut donc être reprise plus tard.</li>
</ul>
<p>Voici donc la principale différence entre une fonction classique et une fonction génératrice. Les fonctions normales ne peuvent pas être mises en pause. Les générateurs peuvent être interrompus puis repris.</p>
<h2>Ce que font les générateurs</h2>
<p>Que se passe-t-il lorsqu’on appelle la fonction génératrice <code>quips()</code> ?</p>
<pre>
> var iter = quips("jorendorff");
[object Generator]
> iter.next()
{ value: "bonjour jorendorff!", done: false }
> iter.next()
{ value: "j'espère que vous appréciez ces billets", done: false }
> iter.next()
{ value: "À la prochaine !", done: false }
> iter.next()
{ value: undefined, done: true }
</pre>
<p>Vous êtes sans doute familier des fonctions classiques et de leur comportement. Lorsqu’elles sont appelées, elles démarrent immédiatement et ne s’arrêtent que lorsqu’elles rencontrent le mot-clé <code>return</code> ou <code>throw</code>. Élémentaire pour tout programmeur en JavaScript.</p>
<p>Un appel à un générateur ressemble à un appel à une fonction classique : <code>quips("jorendorff")</code>. Cependant, quand on appelle un générateur, il ne démarre pas immédiatement. En fait, il renvoie un objet générateur en pause (nommé <code>iter</code> dans l’exemple ci-dessus). On peut considérer cet objet générateur comme un appel de fonction, gelé dans le temps. En particulier, il est mis en pause tout au début de la fonction génératrice, juste avant de démarrer la première ligne de code.</p>
<p>À chaque appel de la méthode <code>.next()</code> de l’objet générateur, l’appel de la fonction se remet en route jusqu’au <code>yield</code> suivant.</p>
<p>C’est pour cette raison qu’à chaque fois que nous avons appelé <code>iter.next()</code> dans l’exemple ci-dessus nous avons obtenu une valeur différente (sous la forme d’une chaîne de caractères).</p>
<p>Lors du dernier appel à <code>iter.next()</code>, nous avons finalement atteint la fin de la fonction génératrice, le champ <code>.done</code> vaut donc <code>true</code>. Atteindre la fin d’une fonction revient à renvoyer <code>undefined</code>, c’est pour cela que la propriété <code>.value</code> du résultat vaut <code>undefined</code>.</p>
<p>C’est sans doute le bon moment pour revenir à <a href="http://people.mozilla.org/~jorendorff/demos/meow.html" hreflang="en" title="Démo chat">la page de démo du chat parlant</a> et manipuler le code pour de bon. Essayez par exemple d’ajouter un <code>yield</code> dans une boucle, que se passe-t-il ?</p>
<p>D’un point de vue technique, chaque fois qu’un générateur utilise <code>yield</code>, le cadre de sa pile (<em>stack frame</em>) — qui contient les variables locales, les arguments, les valeurs temporaires ainsi que la position actuelle de l’exécution dans le générateur — est ôté de la pile. Cependant, le générateur conserve une référence vers ce cadre afin que le prochain appel à <code>.next()</code> puisse réutiliser ce cadre et continuer l’exécution du code.</p>
<p>Il est important de préciser que <strong>les générateurs ne sont pas des <em>threads</em></strong>. Dans les langages utilisant des <em>threads</em>, on peut rencontrer plusieurs parties de code qui fonctionnent en même temps, entraînant habituellement des accès concurrents, un certain indéterminisme et de meilleures performances.</p>
<p>Les générateurs n’agissent pas du tout de cette façon. Quand un générateur fonctionne, il se situe dans le même <em>thread</em> que son appel. L’ordre des exécutions est séquentiel et déterministe, il n’y a pas d’accès concurrents. Contrairement aux systèmes utilisant des <em>threads</em>, un générateur est uniquement suspendu aux endroits correspondants aux <code>yield</code> contenus dans son code.</p>
<p>OK, nous savons maintenant ce que sont les générateurs. Nous avons vu un générateur fonctionner, s’arrêter puis reprendre. Arrive maintenant la grande question : en quoi cette chose bizarre pourrait-elle nous être utile ?</p>
<h2>Les générateurs sont des itérateurs</h2>
<p>La semaine dernière nous avons vu que les itérateurs ES6 ne sont pas seulement une classe native. Ils représentent une extension du langage dans son ensemble. Vous pouvez créer vos propres itérateurs, il suffit d’implémenter deux méthodes : <code>[Symbol.iterator()]</code> et <code>next()</code>.</p>
<p>Malgré tout, implémenter une interface demande toujours un minimum de travail. Voyons à quoi ressemble réellement l’implémentation d’un itérateur. Par exemple, créons un itérateur basique qui compte d’un nombre à un autre, comme le ferait une bonne vieille boucle <code>for(;;)</code> en C.</p>
<pre>
// Cela devrait sonner 3 fois
for (var value of range(0, 3)) {
alert("Ding ! à l'étage numéro " + value);
}
</pre>
<p>Voici une solution qui utilise une classe ES6 (si cette syntaxe vous semble un peu obscure, pas de panique, ce sera l’objet d’un prochain billet).</p>
<pre>
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
} else {
return {done: true, value: undefined};
}
}
}
// On renvoie un nouvel itérateur qui compte de 'start' à 'stop'.
function range(start, stop) {
return new RangeIterator(start, stop);
}
</pre>
<p><a href="http://codepen.io/anon/pen/eNdqop" title="Demo Codepen 1">Essayez ce code.</a></p>
<p>Note : il est nécessaire d’utiliser Firefox Nightly ou Chrome avec une version supérieure ou égale à 42 pour que cet exemple fonctionne.</p>
<p>C’est pour cela qu’implémenter un itérateur se fait comme avec <a href="http://gafter.blogspot.fr/2007/07/internal-versus-external-iterators.html" hreflang="en" title="Billet itérateur Java">Java</a> ou <a href="https://schani.wordpress.com/2014/06/06/generators-in-swift/" hreflang="en" title="Billet générateurs Swift">Swift</a>, ce n’est pas si compliqué ! Mais ce n’est pas vraiment trivial non plus ! Est-ce que ce code contient des erreurs ? Pas facile à dire. Il ne ressemble pas du tout à la boucle <code>for(;;)</code> habituelle que l’on essaie de remplacer : le protocole des itérateurs nous oblige à démanteler la boucle.</p>
<p>À ce stade, vos sentiments concernant les itérateurs sont sans doute mitigés. Ils sont sûrement super à utiliser mais ils semblent difficiles à implémenter.</p>
<p>À tout hasard, n’auriez vous pas idée d’une toute nouvelle structure de contrôle du flux en JavaScript, fonctionnant étape par étape, et qui rendraient les itérateurs beaucoup plus simples à construire ? Puisque nous avons les générateurs, pourquoi ne pas les utiliser ici ?</p>
<p>Essayons :</p>
<pre>
function* range(start, stop) {
for (var i = start; i < stop; i++)
yield i;
}
</pre>
<p><a href="http://codepen.io/anon/pen/GJjVLG" title="Démo Codepen 2">Essayez ce code.</a></p>
<p>Les 4 lignes du générateur ci-dessus remplacent exactement les 23 lignes utilisées précédemment pour implémenter <code>range()</code>, y compris l’intégralité de la classe <code>RangeIterator</code>. Ceci est possible parce que les générateurs sont des itérateurs. Tous les générateurs possèdent une implémentation native de <code>.next()</code> et de <code>[Symbol.iterator]()</code>. Il suffit simplement d’écrire le comportement de la boucle.</p>
<p>Implémenter des itérateurs sans disposer de générateurs, c’est équivalent à devoir écrire un courrier en utilisant uniquement la voie passive. Lorsqu’il est impossible d’exprimer directement ce qu’on a besoin, on se retrouve à formuler des phrases de façon alambiquée. <code>RangeIterator</code> était long et étrange car on l’utilisait pour décrire une boucle sans pouvoir utiliser une syntaxe de boucle. Les générateurs permettent de répondre à ce problème.</p>
<p>Comment peut-on utiliser les générateurs comme des itérateurs ?</p>
<ul>
<li><p>En rendant n’importe quel objet itérable. Il suffit d’écrire une fonction génératrice qui parcourt <code>this</code>, et qui utilise <code>yield</code> sur chaque valeur rencontrée. La fonction obtenue peut être définie comme la méthode <code>[Symbol.iterator]</code> de l’objet.</p></li>
<li><p>En simplifiant les fonctions de construction de tableaux. Si votre fonction retourne un tableau de résultats à chaque appel, comme ceci :</p>
<pre>
// Divise un tableau à une dimension 'icons'
// en tableaux de taille fixe 'rowLength'
function splitIntoRows(icons, rowLength) {
var rows = [];
for (var i = 0; i < icons.length; i += rowLength) {
rows.push(icons.slice(i, i + rowLength));
}
return rows;
}
</pre>
<p>Les générateurs permettent de faire la même chose de manière plus concise :</p>
<pre>
function* splitIntoRows(icons, rowLength) {
for (var i = 0; i < icons.length; i += rowLength) {
yield icons.slice(i, i + rowLength);
}
}
</pre>
<p>
La seule différence de comportement est le fait qu’au lieu de travailler sur tous les résultats à la fois et de renvoyer un tableau les contenant, ceci renvoie un itérateur et les valeurs de retour sont calculées l’une après l’autre, à la demande.
</p>
<li><p>Pour des résultats de grande taille, cela peut également être utile. Il est impossible de construire un tableau infini. Mais vous pouvez renvoyer un générateur qui produit une séquence sans fin, chaque appelant pourra récupérer autant de valeurs que nécessaire à partir de ce générateur.</p></li>
<li><p>Pour « refactorer » des boucles complexes : vous avez une fonction énorme et moche ? Vous souhaitez la scinder en éléments plus simples ? Les générateurs sont des couteaux que vous pouvez ajouter à votre boîte à outils. Lorsque vous êtes face à une boucle complexe, vous pouvez sortir la partie qui produit les données dans une fonction génératrice indépendante. Puis modifier la boucle en utilisant <code>for(var toto of monNouveauGenerateur(args))</code>.
<li><p>Pour créer des outils afin de manipuler les itérables. ES6 ne fournit pas de bibliothèque complète pour filtrer, mapper et bidouiller les ensembles de données itérables. Cependant, les générateurs sont très bien adaptés à la création de ces outils dont vous pourriez avoir besoin, en écrivant simplement quelques lignes de code.
Par exemple, supposons que vous ayez besoin d’un équivalent de <code>Array.prototype.filter()</code> qui fonctionne sur les <code>NodeList</code> du DOM et pas uniquement sur les objets <code>Array</code>. Facile !</p>
<pre>
function* filter(test, iterable) {
for (var item of iterable) {
if (test(item))
yield item;
}
}
</pre>
</li>
</ul>
<p>Alors, est-ce que les générateurs sont utiles ? Évidemment. Ils représentent une façon étonnament simple d’implémenter des itérateurs personnalisés et les itérateurs représentent le nouveau standard pour les données et les boucles dans ES6.</p>
<p>Mais ce n’est pas la seule chose que peuvent faire les générateurs. Ce n’est peut-être même pas la plus importante chose qu’ils peuvent faire !</p>
<h2>Générateurs et code asynchrone</h2>
<p>Voici du code JS que j’ai écrit il y a quelque temps :</p>
<pre>
};
})
});
});
});
});
</pre>
<p>Cela vous rappelle peut-être quelques lignes de votre code. <a href="http://www.html5rocks.com/en/tutorials/async/deferred/" hreflang="en" title="Asynch JS: The Power Of $.Deferred">Les API asynchrones</a> demandent généralement une fonction de rappel (<em>callback</em>), ce qui signifie qu’il faut écrire une fonction anonyme supplémentaire chaque fois qu’on fait quelque chose. Ainsi, si vous avez un code qui fait 3 choses, plutôt que 3 lignes de code, vous aurez 3 niveaux d’indentation dans votre code.</p>
<p>Voici un autre fragment de code JS que j’ai écrit :</p>
<pre>
}).on('close', function () {
done(undefined, undefined);
}).on('error', function (error) {
done(error);
});
</pre>
<p>Les API asynchrones possèdent certaines conventions concernant la gestion des erreurs plutôt que d’utiliser des exceptions. Différentes API possèdent différentes conventions. Dans la plupart des cas, les erreurs sont ignorées par défaut. Dans certains cas, même les cas de terminaisons normales sont ignorés par défaut.</p>
<p>Jusqu’à maintenant, ces problèmes ont été le prix à payer pour utiliser la programmation asynchrone. Nous avons fini par accepter que le code asynchrone n’est pas aussi propre et simple que la version synchrone correspondante.</p>
<p>Les générateurs nous font espérer que ce ne soit plus le cas.</p>
<p><a href="https://github.com/kriskowal/q/tree/v1/examples/async-generators" hreflang="en" title="Q Async - GitHub">Q.async()</a> est un tentative expérimentale pour utiliser les générateurs avec des promesses afin de produire du code asynchrone qui ressemble à du code synchrone.
Par exemple :</p>
<pre>
// Code synchrone pour créer une fonction qui fait du bruit
function makeNoise() {
shake();
rattle();
roll();
}
// Code asynchrone pour faire du bruit.
// Renvoie un objet Promise qui est résolu
// quand nous avons fini de faire du bruit.
function makeNoise_async() {
return Q.async(function* () {
yield shake_async();
yield rattle_async();
yield roll_async();
});
}
</pre>
<p>La principale différence est que, dans la version asynchrone, il faut ajouter le mot-clé <code>yield</code> à chaque appel d’une fonction asynchrone.</p>
<p>Ajouter un détail comme une condition <code>if</code> ou un bloc <code>try-catch</code> dans la version <code>Q.async</code> se fait exactement de la même façon que dans la version synchrone. Comparé à d’autres façons d’écrire du code asynchrone, cela ressemble beaucoup moins à l’apprentissage d’un nouveau langage.</p>
<p>Si vous êtes arrivé jusqu’ici, vous apprécierez sans doute <a href="http://jlongster.com/A-Study-on-Solving-Callbacks-with-JavaScript-Generators" hreflang="en" title="Billet de James Long sur les générateurs">l’article très détaillé de James Long sur ce sujet</a>.</p>
<p>Les générateurs illustrent donc un nouveau modèle de programmation asynchrone qui semble mieux adapté au cerveau humain. Les travaux autour de ces concepts sont toujours en cours. Entre autres choses, une meilleure syntaxe devrait aider. Pour <a href="https://github.com/tc39/ecma262" hreflang="en" title="ES7 - GitHub">la version ES7</a>, il existe <a href="https://github.com/lukehoban/ecmascript-asyncawait" hreflang="en" title="Asyncawait - GitHub">une proposition</a> pour construire des fonctions asynchrones basées à la fois sur les promesses (<em>promises</em>) et les générateurs, inspirées de fonctionnalités similaires existant en C#.</p>
<h2>Quand puis-je commencer à utiliser cette fonctionnalité ?</h2>
<p>Côté serveur, vous pouvez utiliser les générateurs ES6 dès aujourd’hui avec io.js (et avec Node en utilisant l’option —harmony)</p>
<p>Côté navigateur, seuls Firefox 27+ et Chrome 39+ supportent les générateurs ES6 pour le moment. Pour les utiliser à travers le web, vous devrez utiliser un compilateur tel que <a href="http://babeljs.io/" hreflang="en" title="BabelJS">Babel</a> ou <a href="https://github.com/google/traceur-compiler#what-is-traceur" hreflang="en" title="Traceur - GitHub">Traceur</a> pour traduire votre code ES6 en code ES5, plus largement compatible avec les anciens navigateurs.</p>
<p>Quelques remerciements à qui de droit : les générateurs ont d’abord été implémentés dans JS par Brendan Eich ; sa conception suivait de près <a href="https://www.python.org/dev/peps/pep-0255/" hreflang="en" title="PEP 0255">les générateurs Python</a>, inspirés par <a href="http://www.cs.arizona.edu/icon/" hreflang="en" title="Icon">Icon</a>. Ils sont apparus dans Firefox 2.0 <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Nouveaut%C3%A9s_et_historique_de_JavaScript/1.7" hreflang="fr" title="Historique JavaScript 1.7 - MDN">dès 2006</a>. Le chemin vers la standardisation a été chaotique et la syntaxe ainsi que le comportement ont évolué au cours de cette période. Les générateurs d’ES6 ont été implémentés dans Firefox et Chrome par la même personne, <a href="http://wingolog.org/" hreflang="en" title="Andy Wingo">Andy Wingo</a>, un professionnel du compilateur. Ce travail a été sponsorisé par Bloomberg.</p>
<h2><code>yield;</code></h2>
<p>Il y a encore à dire sur les générateurs. Nous n’avons pas évoqué les méthodes <code>.throw()</code> et <code>return()</code>, les arguments optionnels de <code>.next()</code> ou encore la syntaxe de l’expression <code>yield*</code>. Cependant, je pense que ça suffira pour ce billet, déjà suffisamment déconcertant. Comme le font les générateurs, il vaut mieux faire une pause et reprendre une autre fois.</p>
<p>La semaine prochaine, nous changerons d’allure. Nous avons abordé deux sujets assez conséquents, l’un après l’autre. Ne pourrions-nous pas parler d’une fonctionnalité ES6 qui ne changera pas votre vie ? Quelque chose de simple et de manifestement utile ? Quelque chose qui vous fera sourire ? ES6 possède aussi quelques fonctionnalités de ce genre.</p>
<p>À venir : une fonctionnalité <em>qui va immédiatement trouver sa place</em> dans le code que vous écrivez tous les jours. Rejoignez-nous la semaine prochaine pour une explication détaillée sur les patrons de chaînes (<em>template strings</em>) d’ES6 !</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : les itérateurs et la boucle for-ofurn:md5:f1b4043180d618a9f44650eed38296ee2015-05-23T20:15:00+02:002015-05-23T22:04:52+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em><a href="https://tech.mozfr.org/post/2015/05/19/ES6-en-details-%3A-Une-introduction" hreflang="fr" title="ES en détails : une introduction">Suite de la traduction</a>, qui continue la série d’articles de <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a>. L’article original se trouve <a href="https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/">ici</a>.</em></p>
<p><em>Merci aux traducteurs et relecteurs :) Marine, Mentalo, Benjamin, Amarok, Lucas, Ilphrin et Goofy !</em></p>
<hr /> <p><em><a href="https://hacks.mozilla.org/category/es6-in-depth/" hreflang="en" title="ES6 in depth">ES6 en détails</a> 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é).</em></p>
<p>Comment itérer sur les éléments d’un tableau ? Lorsque JavaScript est apparu, il y a vingt ans de ça, on aurait fait ainsi :</p>
<pre>
for (var index = 0; index < monTableau.length; index++) {
console.log(monTableau[index]);
}
</pre>
<p>Depuis ES5, on peut utiliser la méthode native <code>forEach</code> :</p>
<pre>
monTableau.forEach(function (value) {
console.log(value);
});
</pre>
<p>C’est un petit peu plus court, mais il y a un léger inconvénient : il n’est pas possible de sortir de cette boucle avec une instruction <code>break</code> ou de faire retourner la fonction englobante avec une instruction <code>return</code>.</p>
<p>Ce serait effectivement agréable s’il y avait une syntaxe de boucle <code>for</code> itérant directement sur les éléments du tableau.</p>
<p>Quid d’une boucle <code>for-in</code> ?</p>
<pre>
for (var index in monTableau) {
// ne faites pas ceci
console.log(monTableau[index]);
}</pre>
<p>C’est une mauvaise idée pour plusieurs raisons :</p>
<ol>
<li>Les valeurs assignées pour l’index dans ce code sont les chaînes de caractères "0", "1", "2", et ainsi de suite : ce ne sont pas réellement des nombres. Étant donné qu’on ne souhaite sûrement pas manipuler des chaînes de façon arithmétique ("2" + 1 == "21"), c’est, a minima, peu pratique ;</li>
<li>Le corps de la boucle ne va pas seulement s’exécuter pour chaque élément du tableau, mais également pour toutes les propriétés non-natives que l’on pourrait avoir ajoutées. Par exemple, si votre tableau possède une propriété énumérable <code>monTableau.nom</code>, alors cette boucle va s’exécuter une fois de plus, avec <code>index == "nom"</code>. Même les propriétés présentes sur la chaîne de prototypes du tableau peuvent être visitées ;</li>
<li>Le plus étonnant dans tout ça est que, dans certaines circonstances, ce code va boucler sur les éléments du tableau dans un ordre arbitraire.</li>
</ol>
<p>Pour faire court, <code>for-in</code> a été pensé pour travailler avec de bons vieux objets (type <code>Object</code>) dont les clés sont des chaînes de caractères. Pour les tableaux (type <code>Array</code>), ce n’est pas génial.</p>
<h2>La puissante boucle <code>for-of</code></h2>
<p>Vous souvenez-vous de la semaine dernière, lorsque j’ai promis qu’ES6 ne casserait pas le code JavaScript que vous avez déjà écrit ? En fait, il existe des millions de sites Web qui dépendent du comportement de <code>for-in</code> — oui, même de son comportement sur les tableaux. Il n’a donc jamais été question de « réparer » <code>for-in</code> afin qu’elle soit plus utile pour manipuler avec des tableaux. Le seul moyen pour ES6 d’améliorer les choses était d’ajouter une nouvelle forme de syntaxe de boucle.</p>
<p>La voilà :</p>
<pre>
for (var valeur of monTableau) {
console.log(valeur);
}
</pre>
<p>Hmm. Après toute cette publicité, ça ne semble pas si impressionnant, n’est-ce pas ? Pourtant, nous allons voir que <code>for-of</code> a plus d’un tour dans son sac. Pour l’instant, notez simplement que :</p>
<ol>
<li>C’est la syntaxe la plus concise et la plus directe pour boucler sur les éléments d’un tableau ;</li>
<li>Celle-ci permet d’éviter les pièges de <code>for-in</code> ;</li>
<li>Contrairement à <code>forEach()</code>, elle est compatible avec les instructions <code>break</code>, <code>continue</code> et <code>return</code>.</li>
</ol>
<p>La boucle <code>for-<strong>in</strong></code> est faite pour boucler sur les propriétés d’un objet.</p>
<p>La boucle <code>for-<strong>of</strong></code> est faite pour boucler sur des données — comme les valeurs d’un tableau.</p>
<p>Mais ce n’est pas tout.</p>
<h2><code>for-of</code> fonctionne également avec les autres collections</h2>
<p><code>for-of</code> n’est pas restreint aux tableaux. Cela fonctionne également avec la plupart des objets qui agissent comme des tableaux, telles que les <code>NodeList</code> du DOM. Cela fonctionne également sur les chaînes de caractères, en considérant que la chaîne est une séquence de caractères Unicode :</p>
<pre>
for (var chr of "♂ ♀"){
alert(chr);
}
</pre>
<p>Cela fonctionne également avec les objets <code>Map</code> et <code>Set</code>.</p>
<p>Oh, désolé si vous n’avez jamais entendu parler des objets <code>Map</code> et <code>Set</code>, ceux-ci sont des nouveautés apportées par ES6. Nous en parlerons plus tard dans un article dédié. Si vous avez déjà utilisé des <code>Maps</code> (dictionnaires) et <code>Sets</code> (ensembles) dans d’autres langages, vous ne serez pas très surpris.</p>
<p>Par exemple, un objet <code>Set</code> peut être utilisé pour éliminer des doublons :</p>
<pre>
// créons un ensemble à partir d'un tableau de mots
var motsUniques = new Set(mots);
</pre>
<p>Une fois que l’on a un ensemble (<code>Set</code>), que se passe-t-il si on veut itérer sur son contenu ? Facile :</p>
<pre>
for (var mot of motsUniques) {
console.log(mot);
}
</pre>
<p>Une <code>Map</code> est légèrement différente : les données que celle-ci contient sont des paires de clés-valeurs, il faudra donc utiliser l’affectation « destructurée » (<em>destructuring</em>) pour séparer la clé et la valeur en deux variables distinctes :</p>
<pre>
for (var [clé, valeur] of annuaireMap) {
console.log("Le numéro de téléphone de "+clé +" est " + valeur);
}
</pre>
<p>La décomposition est une autre fonctionnalité, également apparue avec ES6 qui fera un sujet idéal pour un prochain article que j’écrirai.</p>
<p>Maintenant, vous avez une idée du tableau : JS possède déjà un certain nombre de structures de données pour des collections, et il y en a encore plus à venir. <code>for-of</code> a été pensé pour être l’instruction de boucle ultime à utiliser avec toutes ces structures.</p>
<p><code>for-of</code> ne fonctionne pas avec des objets tout simples, mais si vous voulez itérer sur les propriétés d’un objet, vous pouvez soit utiliser <code>for-in</code> (c’est sa raison d’être), soit la fonction native <code>Object.keys()</code> :</p>
<pre>
// affiche toutes les propriétés propres énumérables
// d'un objet dans la console
for (var clé of Object.keys(monObjet)) {
console.log(clé + ": " + monObjet[clé]);
}
</pre>
<h2>Sous le capot</h2>
<blockquote><p>« Les bons artistes copient, les très bons artistes volent » — Pablo Picasso</p></blockquote>
<p>Un thème récurrent d’ES6 est le fait que les nouvelles fonctionnalités ajoutées au langage ne viennent pas de nulle part. La plupart ont été essayées et se sont avérées utiles dans d’autres langages.</p>
<p>La boucle <code>for-of</code>, par exemple, rappelle des instructions de boucle similaires en C++, Java, C#, et Python. Comme celles-ci, elle fonctionne avec des structures de données différentes, fournies par le langage et sa bibliothèque standard. Mais c’est également un ajout au langage.</p>
<p>De même qu’avec les instructions <code>for</code> ou <code>forEach</code> dans ces autres langages, <strong><code>for-of</code> fonctionne entièrement à l’aide d’appels de fonctions</strong>. Ce que les tableaux (objets <code>Array</code>), dictionnaires (objets <code>Map</code>), ensembles (objets <code>Set</code>) et les autres objets ont en commun est le fait qu’ils ont chacun une méthode <code>iterator</code> (NdT : pour « itérateur »).</p>
<p>Il existe une autre catégorie d’objets qui peuvent aussi avoir une méthode <code>iterator</code> : n’importe quel objet.</p>
<p>De la même manière que lorsqu’on ajoute une méthode <code>monObjet.toString()</code> à n’importe quel objet, JS sait comment convertir cet objet en une chaîne de caractères, on peut ajouter la méthode <code>monObjet<a href="https://tech.mozfr.org/post/2015/05/20/Symbol.iterator" title="Symbol.iterator">Symbol.iterator</a>()</code> à n’importe quel objet, et, soudainement, JS sait comment itérer sur cet objet.</p>
<p>Par exemple, imaginons que vous utilisiez jQuery, et que, bien que vous soyez très fan de <code>.each()</code>, vous aimeriez que les objets jQuery fonctionnent également avec <code>for-of</code>. Voici la manière de procéder :</p>
<pre>
// Comme les objets jQuery se comportent comme des tableaux,
// donnons-leur la même méthode iterator que celle de l'objet Array
jQuery.prototype[Symbol.iterator] =
Array.prototype[Symbol.iterator];
</pre>
<p>OK, je sais ce que vous êtes en train de vous dire. Cette syntaxe <code><a href="https://tech.mozfr.org/post/2015/05/20/Symbol.iterator" title="Symbol.iterator">Symbol.iterator</a></code> est bizarre. Que se passe-t-il ici ? C’est en lien avec le nom de la méthode. Le comité de standardisation aurait pu appeler cette méthode <code>.iterator()</code>, or dans ce cas, votre code aurait déjà pu comporter des objets ayant une méthode <code>.iterator()</code>, et cela aurait pu être une source de confusion. C’est pourquoi le standard préfère utiliser un symbole plutôt qu’une chaine de caractères comme nom de la méthode.</p>
<p>Les symboles sont apparus avec ES6, et nous vous expliquerons tout sur eux — vous l’avez deviné — dans un prochain article de blog. Pour l’instant, tout ce que vous avez besoin de savoir, c’est que le standard peut définir un symbole entièrement nouveau, comme <code>Symbol.iterator</code>, et qu’il est garanti que cela ne créera pas de conflit avec du code existant. Le compromis que cela implique est une syntaxe un peu étrange. Mais c’est un petit prix à payer pour cette nouvelle fonctionnalité versatile et la garantie d’une excellente rétro-compatibilité.</p>
<p>Un objet qui possède une méthode <code><a href="https://tech.mozfr.org/post/2015/05/20/Symbol.iterator" title="Symbol.iterator">Symbol.iterator</a>()</code> est appelé un itérable. Dans les semaines qui viennent, nous verrons que le concept d’objets itérables est utilisé à travers tout le langage, pas seulement avec <code>for-of</code> mais également avec les constructeurs des objets <code>Map</code> et <code>Set</code>, avec les affectations déstructurées et avec le nouvel opérateur « <em>spread</em> » (NdT : ou « opérateur de décomposition »).</p>
<h2>Les objets itérateurs</h2>
<p>Il est probable que vous n’ayez jamais à implémenter un itérateur de A à Z. On verra pourquoi la semaine prochaine. Cependant, pour être tout à fait complet, regardons de près à quoi ressemble un objet itérable (si vous sautez cette section, vous ne manquerez que quelques détails techniques).</p>
<p>Une boucle <code>for-of</code> commence par appeler la méthode <code><a href="https://tech.mozfr.org/post/2015/05/20/Symbol.iterator" title="Symbol.iterator">Symbol.iterator</a>()</code> de la collection utilisée. Ceci renvoie un nouvel itérateur qui peut être n’importe quel objet possédant une méthode <code>.next()</code> ; la boucle <code>for-of</code> appellera cette méthode de façon répétitive, une fois pour chaque itération. Par exemple, voici l’itérateur le plus simple auquel je peux penser :</p>
<pre>
var itérateurPleinDeZéros = {
[Symbol.iterator]: function () {
return this;
},
next: function () {
return {done: false, value: 0};
}
};
</pre>
<p>À chaque appel de la méthode <code>.next()</code>, le même résultat sera renvoyé et communiquera les informations suivantes à la boucle <code>for-of</code> :</p>
<ol>
<li>nous n’avons pas encore terminé les itérations ;</li>
<li>la prochaine valeur est 0.</li>
</ol>
<p>Cela signifie que la boucle <code>for (valeur of itérateurPleinDeZéros) {}</code> sera une boucle infinie. Évidemment, un itérateur utilisé dans du code ne sera pas aussi trivial.</p>
<p>Ce concept d’itérateur, avec les propriétés <code>.done</code> et <code>.value</code>, est légèrement différente de celui utilisé dans les autres langages. En Java, les itérateurs possèdent des méthodes séparées <code>.hasNext()</code> et <code>.next()</code>. En Python, ils possèdent une seule méthode <code>.next()</code> qui appelle <code>StopIteration</code> lorsqu’il n’y a plus de valeur. Fondamentalement, ces trois conceptions renvoient les mêmes informations.</p>
<p>Un itérateur peut également implémenter les méthodes optionnelles <code>.return()</code> et <code>.throw(exc)</code>. La boucle <code>for-of</code> appelle la méthode <code>.return()</code> si la boucle est interrompue de façon prématurée, à cause d’une exception, d’une instruction <code>break</code> ou <code>return</code>.</p>
<p>Un itérateur peut également implémenter la méthode <code>return()</code> s’il est nécessaire de procéder à un nettoyage ou de libérer des ressources utilisées. La plupart des objets itérateurs n’auront pas à implémenter cette méthode. La méthode <code>throw(exc)</code> est un cas encore plus spécifique : les boucles <code>for-of</code> ne les appellent jamais, on en parlera la semaine prochaine.</p>
<p>Maintenant que nous connaissons tous les détails, nous pouvons nous intéresser à une boucle <code>for-of</code> simple et la ré-écrire avec les appels des méthodes sous-jacentes.</p>
<p>Commençons avec la boucle <code>for-of</code> :</p>
<pre>
for (VAR of ITERABLE) {
INSTRUCTIONS
}
</pre>
<p>Voici un équivalent, un peu brut, utilisant les méthodes sous-jacentes ainsi que quelques variables temporaires :</p>
<pre>
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!result.done) {
VAR = result.value;
INSTRUCTIONS
$result = $iterator.next();
}
</pre>
<p>Ce code n’illustre pas l’utilisation de la méthode <code>.return()</code>. On pourrait l’ajouter mais je pense que cela ajouterait plus de confusion. Les boucles <code>for-of</code> sont simples à utiliser, malgré cela, il se passe beaucoup de choses en arrière-plan.</p>
<h2>Quand puis-je commencer à utiliser cette fonctionnalité ?</h2>
<p>Les boucles <code>for-of</code> sont supportées par les versions actuelles de Firefox. Elles sont supportés dans Chrome sous réserve d’activer l’option « Activer la fonctionnalité expérimentale JavaScript » accessible via l’URL <code>chrome://flags</code>. Cela fonctionne également dans le prochain navigateur Edge (nom de code « Spartan ») de Microsoft. En revanche, cette fonctionnalité n’est pas disponible dans les différentes versions d’Internet Explorer. Si vous souhaitez utiliser cette nouvelle syntaxe et que devez prendre en compte IE et Safari, vous pouvez utiliser un compilateur tel que <a href="http://babeljs.io/" title="BabelJS">Babel</a> ou <a href="https://github.com/google/traceur-compiler#what-is-traceur" hreflang="en" title="Traceur - GitHub">Traceur</a> de Google pour traduire votre code ES6 en code ES5, plus largement compatible avec les anciens navigateurs.</p>
<p>Côté serveur, pas besoin d’installer un compilateur - vous pouvez utiliser les boucles <code>for-of</code> dès aujourd’hui avec io.js (et avec Node en utilisant l’option <code>--harmony</code>)</p>
<p>(Mise à jour : j’ai oublié de mentionner que <code>for-of</code> est désactivé par défaut dans Chrome. Merci à Oleg d’avoir signalé cet oubli dans les les commentaires.) (NdT : voir <a href="https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/comment-page-1/#comment-17608" hreflang="en" title="Commentaire Oleg">ce commentaire</a>).</p>
<h2><code>{done: true}</code></h2>
<p>Whaou !</p>
<p>Bon, c’est tout pour aujourd’hui, mais nous n’en n’avons toujours pas fini avec la boucle <code>for-of</code>.</p>
<p>Il y a encore une nouvelle sorte d’objet apparue avec ES6 et qui fonctionne très bien avec les boucles <code>for-of</code>. Je n’en ai pas parlé car c’est le sujet de l’article de la semaine prochaine. Je pense que cette nouvelle fonctionnalité est la plus magique d’ES6. Si vous ne l’avez jamais utilisé dans des langages comme Python et C#, vous serez vraisemblablement déstabilisé au début. Ce sera la manière la plus simple d’écrire un itérateur, un atout pour le <em>refactoring</em> et cela pourrait changer la façon d’écrire du code asynchrone tant au niveau du navigateur que du serveur.</p>
<p>Rejoignez-nous donc la semaine prochaine pour une étude approfondie des générateurs dans ES6.</p>
<style>
pre { white-space: pre;}
</style>
ES6 en détails : une introductionurn:md5:7b17d46a31579af42a7aeb6cb0b759f02015-05-19T22:12:00+02:002015-05-19T22:12:00+02:00sphinxJavaScriptECMAScript6ES6ES6 en détailsHacks<p><em>Ce billet est une traduction de <a href="https://hacks.mozilla.org/2015/04/es6-in-depth-an-introduction/" title="ES6 In Depth: An introduction">cet article</a>, écrit par <a href="https://twitter.com/jorendorff" title="@jorendorff">Jason Orendorff</a> qui participe au développement du moteur JavaScript de Firefox. Ce billet sera le premier d’une série de traductions, chaque billet décrivant une nouvelle fonctionnalité apportée par ECMAScript 6.</em></p>
<p><em>Merci à Marine, Mentalo, Lucas et Benjamin pour la traduction :)</em></p>
<hr />
<p>Bienvenue dans cette série hebdomadaire que sera « ES6 en détails » ! Nous explorerons ECMAScript 6 (ES6), la nouvelle édition, imminente, du langage JavaScript. ES6 contient de nouvelles et nombreuses fonctionnalités qui feront de JavaScript (JS) un langage plus puissant et expressif. Chaque semaine, nous détaillerons une de ces fonctionnalités. Avant de commencer, arrêtons-nous quelques minutes sur ce qu’est ES6 et ce qu’on peut en attendre.</p>
<h2>Quel est le champ d’application d’ECMAScript ?</h2>
<p>Le langage de programmation JavaScript a été standardisé par l’organisation de standardisation ECMA sous le nom d’ECMAScript (ES). Entre autres choses, ECMAScript définit :</p>
<ul>
<li><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Grammaire_lexicale">La syntaxe du langage</a> : les règles d’analyse lexicale du langage, les mots-clés, les instructions, les déclarations, les opérateurs, etc.</li>
<li><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Structures_de_donn%C3%A9es">Les types</a> : booléens, nombres, chaînes de caractères, objets, etc.</li>
<li><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/H%C3%A9ritage_et_cha%C3%AEne_de_prototypes">Les concepts de prototype et d’héritage</a></li>
<li><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux">La bibliothèque de fonctionnalités et d’objets natifs standards</a> : <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/JSON">JSON</a>, <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Math"><code>Math</code></a>, <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array">les méthodes pour l’objet <code>Array</code></a>, <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object">les méthodes d’inspection des objets</a>, etc.</li>
</ul>
<p>ECMAScript ne définit rien qui concerne HTML ou CSS ni même les <a href="https://developer.mozilla.org/fr/docs/Web/API">API Web</a> telles que le DOM (Document Object Model). Ces éléments sont définis dans des standards distincts. ECMAScript couvre les aspects de JavaScript qui interviennent dans un navigateur ou dans un autre environnement (par exemple <a href="https://nodejs.org/">node.js</a>).</p>
<h2>Le nouveau standard</h2>
<p>La semaine dernière (NdT : le 16 avril 2015), la version finale de la spécification de la sixième édition du langage ECMAScript, a été proposée à l’Assemblée Générale ECMA pour relecture. Qu’est-ce que cela signifie ?</p>
<p>Cela signifie que cet été, <strong>nous aurons un nouveau standard pour le cœur du langage de programmation Javascript.</strong></p>
<p>C’est une grande nouvelle. Un nouveau standard pour le langage JavaScript ne se fait pas en un jour. La dernière édition, ES5, est sortie en 2009. Le comité de standardisation travaille sur ES6 depuis cette date.</p>
<p>ES6 est une évolution majeure du langage. Votre code JS continuera à fonctionner. ES6 a été conçu pour apporter un maximum de compatibilité avec le code existant. En fait, de nombreux navigateurs supportent d’ores et déjà les fonctionnalités d’ES6, et les implémentent continuellement. Votre code JS est <em>déjà</em> exécuté par les navigateurs qui implémentent les fonctionnalités d’ES6. Si vous n’avez pas constaté de problèmes de compatibilité à ce jour, vous n’en verrez probablement jamais.</p>
<h2>Compter jusqu’à 6</h2>
<p>Les éditions précédentes du standard d’ECMAScript furent numérotées 1, 2, 3, et 5.</p>
<p>Qu’est-il arrivé à la quatrième édition ? Elle était planifiée — et le travail avait bien avancé — mais elle a été stoppée car considérée comme trop ambitieuse (elle proposait, par exemple, un système avancé de typage statique optionnel, doté de types génériques et d’une inférence de types).</p>
<p>ES4 a été un sujet controversé. Quand le comité de standardisation a finalement décidé d’arrêter ce projet, ses membres ont décidé de publier une version plus modeste d’ES5, puis ont commencé à travailler sur d’autres fonctionnalités. Ce travail a été appelé « Harmony », et c’est pourquoi la spécification ES5 contient ces deux phrases :</p>
<blockquote>
<p>« ECMAScript est un langage passionnant et son évolution n’est pas figée. Une amélioration technique considérable se poursuivra dans les futures éditions de la spécification. »</p>
</blockquote>
<p>Cette déclaration peut être lue comme une promesse faite pour ECMAScript 6.</p>
<h2>Les promesses tenues</h2>
<p>ES5, la version du langage datant de 2009, a introduit <code>Object.create()</code>, <code>Object.defineProperty()</code>, les <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions/get">accesseurs</a> et les <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions/set">mutateurs</a> (aussi respectivement appelés getters et setters), le <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Strict_mode">mode strict</a> et les objets JSON. J’ai déjà utilisé toutes ces fonctionnalités, et j’aime ce qu’ES5 a apporté à ce langage. Cependant, force est de constater que ces fonctionnalités eurent assez peu d’impact sur la façon d’écrire du code JS. Selon moi, l’innovation majeure a probablement été les nouvelles méthodes sur les tableaux : <code>.map()</code>, <code>.filter()</code>, et ainsi de suite.</p>
<p>Eh bien ES6 est différent. Il est le fruit d’années de travail harmonieux. Et il regorge de nouvelles fonctionnalités pour le langage et sa bibliothèque. C’est, à ce jour, la mise à jour <em>la plus importante</em> de JS. Les nouvelles fonctionnalités vont des fonctions basiques, comme les fonctions fléchées ou l’interpolation de chaînes de caractères, à de nouveaux concepts qui vont vous en mettre plein la vue comme les proxies et les générateurs.
ES6 va changer la manière dont vous développez en JS.</p>
<p>Le but de cette série est de vous montrer comment, en examinant les nouvelles fonctionnalités qu’ES6 apporte aux développeurs JavaScript.</p>
<p>Nous démarrerons par la classique « fonctionnalité manquante » que j’attends impatiemment en Javascript depuis une décennie.</p>
<p>Rendez-vous la semaine prochaine pour découvrir les itérateurs et la boucle <code>for-of</code> d’ES6.</p>