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

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 :

<script language="JavaScript">
<!--
  document.bgColor = "brown";  // red
// -->
</script>

Les anciens navigateurs y voient uniquement deux balises non supportées et un commentaire, seuls les plus récents reconnaissent du code JavaScript.

Pour supporter cette vieille « bidouille », le moteur JavaScript de votre navigateur traite donc les caractères <!-- comme un commentaire de ligne. Cela fait partie du langage depuis longtemps, et ça fonctionne toujours aujourd’hui, pas uniquement pour la balise <script> mais aussi pour tout le code JS. Cela fonctionne même dans Node.

Pour la première fois, ce type de commentaire est standardisé avec ES6. Mais nous ne sommes pas ici pour parler de cette flèche-là.

La flèche --> 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 --> qui est considéré comme un commentaire.

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, --> peut être compris comme un opérateur de JavaScript, l’opérateur « va jusqu’à » !

function countdown(n) {
  while (n --> 0)  // "n va jusqu'à zéro"
    alert(n);
  décollage();
}

Ce code fonctionne vraiment. La boucle itère jusqu’à ce que n 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 se trouve sur Stack Overflow.

Bien sûr, il y a aussi l’opérateur d’infériorité <=. Vous trouverez peut-être d’autres flèches cachées dans votre code JS. Faisons les comptes… Une flèche semble manquer à l’appel.

<!-- commentaire sur une ligne
--> l’opérateur « va jusqu’à »
<= inférieur ou égal à
=> ???

Qu’est-il arrivé au => ? C’est ce que nous allons voir aujourd’hui.

Commençons par parler un peu des fonctions.

Les expressions de fonctions sont partout

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.

Par exemple, supposons que vous vouliez dire au navigateur quoi faire quand l’utilisateur clique sur un bouton spécifique. Vous commencez à écrire :

$("#confetti-btn").click(

La méthode .click() de jQuery prend un argument : une fonction. Aucun problème, vous pouvez l’écrire ici :

$("#confetti-btn").click(function (event) {
  jouerTrompette();
  lancerConfettis();
});

É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, de nombreux langages n’avaient pas cette fonctionnalité. Bien sûr, dès 1958, Lisp avait des expressions de fonctions, aussi appelées fonctions lambda. Toutefois, C++, Python, C#, et Java ont vécu des années sans elles.

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.

Malheureusement, de tous les langages dont j’ai parlé, JavaScript est celui qui possède la syntaxe la moins concise pour les fonctions lambdas.

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

Une nouvelle flèche à votre carquois

ES6 introduit une nouvelle syntaxe pour écrire des fonctions.

// ES5
var selected = allJobs.filter(function (job) {
  return job.isSelected();
});
// ES6
var selected = allJobs.filter(job => job.isSelected());

Lorsque vous avez besoin d’une fonction simple qui utilise un seul argument, vous pouvez utiliser la nouvelle syntaxe fléchée : Identifiant => expression. Il n’y a plus besoin d’écrire function, return, les parenthèses, les accolades, et le point-virgule.

En ce qui me concerne, j’accueille cette fonctionnalité avec joie vu le nombre de fois où j’ai écrit functoin avant d’avoir à le corriger.

Pour écrire une fonction avec plusieurs arguments (ou aucun argument, ou avec des paramètres du reste ou avec des valeurs par défaut, ou qui utilise une décomposition), vous devrez ajouter des parenthèses autour de la liste d’arguments.

// ES5
var total = values.reduce(function (a, b) {
  return a + b;
}, 0);

// ES6
var total = values.reduce((a, b) => a + b, 0);

Je trouve cela plutôt joli. Les fonctions fléchées fonctionnent aussi bien avec les outils fournis par les bibliothèques telles que Underscore.js et Immutable. En fait, les exemples de la documentation d’Immutable sont tous écrits en ES6, la plupart d’entre eux utilisent déjà les fonctions fléchées.

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 :

// ES5
$("#confetti-btn").click(function (event) {
  jouerTrompette();
  lancerConfettis();
});

Avec ES6, ça ressemble à :

// ES6
$("#confetti-btn").click(event => {
  jouerTrompette();
  lancerConfettis();
});

Ici, ça n’est qu’une amélioration mineure. En revanche, l’effet sur du code utilisant des promesses (Promises) peut être significatif : l’empilement des lignes avec }).then(function (result) { sera drastiquement réduit. Notez que la fonction fléchée ne renvoie pas automatiquement une valeur. Pour cela, il faut utiliser l’instruction return. 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 :

// crée un nouvel objet jouets vide pour chaque chiot

var jouets = chiots.map(chiot => {});   // BUG !
var jouets = chiots.map(chiot => ({})); // ok

En effet, malheureusement, un objet vide {} et un bloc vide {} se ressemblent trait pour trait.

Avec ES6, la règle est la suivante : une accolade { 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 chiot => {} est donc silencieusement interprété comme une fonction fléchée qui ne fait rien et qui renvoie undefined.

Encore plus déroutant, un littéral objet comme {clé: valeur} ressemble à un bloc contenant une déclaration étiquetée, du moins c’est de cette façon que le comprend le moteur JavaScript. Heureusement { est le seul caractère ambigu, rappelez-vous qu’il faut bien entourer le littéral objet avec des parenthèses.

Encore this ?

Le comportement entre les fonctions ordinaires et les fonctions fléchées est légèrement différent. Les fonctions fléchées n’ont pas leur propre valeur de this. La valeur de this à l’intérieur d’une fonction fléchée est toujours héritée depuis la portée englobante.

Avant de voir ce que ça donne en pratique, prenons un peu de recul. Comment fonctionne this en JavaScript ? D’où vient sa valeur ? Il n’y a pas de réponse simple. Si cela vous semble simple, c’est parce que vous l’utilisez depuis longtemps !

Nous nous posons souvent cette question car les fonctions définies avec function reçoivent automatiquement une valeur pour this, qu’elles le veuillent ou non. N’avez-vous jamais écrit ceci ?

{
  ...
  addAll: function addAll(pieces) {
    var self = this;
    _.each(pieces, function (piece) {
      self.add(piece);
    });
  },
  ...
}

Ici, vous auriez voulu simplement écrire this.add(piece) 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 window ou undefined. La variable temporaire self sert à introduire la valeur externe de this dans la fonction interne (une autre solution est l’utilisation de .bind(this) sur la fonction interne ; aucune des deux n’est particulièrement jolie).

Avec ES6, cette bidouille ne sera plus nécessaire si vous respectez les règles suivantes :

  • Utilisez les fonctions non-fléchées pour les méthodes qui seront appelées par la syntaxe objet.méthode(). Ce sont les fonctions qui recevront une valeur significative de this via leur appelant ;
  • Utilisez les fonctions fléchées pour tout le reste.
// ES6
{
  ...
  addAll: function addAll(pieces) {
    _.each(pieces, piece => this.add(piece));
  },
  ...
}

Avec ES6, notez que la méthode addAll reçoit this à partir de sa fonction appelante. La fonction interne est une fonction fléchée, elle hérite donc du this contenu dans la portée englobante.

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é :

// syntaxe ES6
{
  ...
  addAll(pieces) {
    _.each(pieces, piece => this.add(piece));
  },
  ...
}

Entre les méthodes et les fonctions fléchées, je n’écrirai plus jamais function. Enfin !

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 arguments non plus. Bien sûr, avec ES6, il vaut mieux utiliser un paramètre du reste ou une valeur par défaut.

Utiliser les flèches pour percer la noirceur de l’informatique

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.

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 λ-calcul (λ 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 ».

Mais qu’est-ce que le λ-calcul ? Qu’est-ce que signifie « modèle de calcul » ?

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.

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.

Voici un exemple d’un « programme » qu’un mathématicien pourrait écrire en utilisant la λ notation de Church.

fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))

La fonction équivalente, écrite en JavaScript, ressemblerait à :

var fix = f => (x => f(v => x(x)(v)))
               (x => f(v => x(x)(v)));

Cela signifie que JavaScript contient une implémentation fonctionnelle du λ-calcul. Le λ-calcul fait partie de JavaScript.

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 les nombres de Church et les combinateurs à point fixe pour essayer de les manipuler dans la console ou dans l’ardoise de développement 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.

Quand pourrais-je décocher ces flèches ?

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.

Les fonctions fléchées sont aussi implémentées dans la dernière version de Microsoft Edge. Elles sont aussi disponibles dans Babel, Traceur, et TypeScript si jamais vous souhaitez les utiliser sur le Web dès aujourd’hui.

Le sujet de notre prochain article sera une des fonctionnalités étranges d’ES6. Nous verrons que typeof x 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…

Rendez-vous donc la semaine prochaine pour explorer les symboles ES6 en détails.