Enfin apprendre et comprendre le JavaScript !

ES7 est déjà dans nos chaumières et, en plus d'être « à peu près au point » avec ES5, vous n'avez toujours pas digéré ES6 !

Quelle est concrètement la différence entre TypeScript et JavaScript ?
Quelle est concrètement la différence entre TypeScript et JavaScript ?

Il va être temps de nous y pencher de plus près sur ce blog pour aborder la suite de l'aventure JavaScript sereinement. Cet article à pour but, de vous en apprendre plus sur les versions ES3 et ES5 de JavaScript dites « Vanilla JavaScript » tout en les comparant à des équivalences ES6 dites « Harmony JavaScript ». Cela nous permettra de comprendre en quoi ces améliorations peuvent nous aider au quotidien.

Nous allons éplucher les fonctionnalités dans un ordre logique d'apprentissage et explorer les mécaniques sous-jacentes. Nous allons même nous permettre, en amont, de réviser un peu les bases. Ça risque d'être long alors, de la même manière que cet article va être publié en plusieurs mise à jour, n'hésitez pas à le lire en plusieurs fois !

A curious JavaScript Story

C'est l'histoire d'un gars, Netscape, qui se dit : « ok, quand on va écrire des lignes dans un fichier, et qu'on va faire lire ce fichier à un programme, celui-ci va faire les trucs cool que les lignes lui disent de faire. » ; idée presque révolutionnaire puisque c'est déjà ce que fait n'importe quel programme concurrent. « Certe, mais mon code, il sera pas transformé en charabia pour machine à l'avance ; il sera lu et pris en compte directement ! ».

Notre Nets l'invente pas pour rien ce programme (ou langage comme est appelé cette idée par ceux qui y ont pensé avant lui). Son idée à lui, c'est de le placer dans son navigateur, qui permet de surfer sur Internet, pour faire des trucs cool dedans, comme interagir avec le contenu d'une page ; ce qu'il fait. Le gars sait pas trop comment appeler ça et fini par l'appeler JavaScript car à l'époque, ça lui permet de se faire un peu de publicité sur le dos de son collègue Java (ou inversement proportionnel).

L'idée du gars est tellement cool que Microsoft se dit qu'il va faire pareil, et appelle ça du JScript (histoire de se faire de la pub aussi quoi), et de le placer dans son navigateur fétiche à lui, appelé Internet Explorer. Mais Nets a autant les boules qu'il est sympa. Il décide, pour éviter que d'autres gars comme Mic fassent n'importe quoi avec son JavaScript, de demander à son pote Ecma International (de son petit nom Ecma) d'expliquer et d'user de son réseau pour faire savoir que le JavaScript « ça marche comme ça, et pas autrement », chose que Ecma fait et consigne dans un papier sobrement intitulé : Standard ECMA-262. Dans ce papier naît alors le nouveau standard ECMAScript. Grâce à ça, des mecs peuvent inventer des trucs sympa comme le Flash et les gens comme Mic peuvent arrêter de faire n'importe quoi.

Nets était fier de son JavaScript car il avait inventé « un modèle objet orienté prototype, à typage faible mais dynamique et a portée statique » méga performant pour du langage de script (comprenez ici « non compilé »). Après moult rebondissements cela s'est soldé par une version ECMASript 3. Appelons là, ES3 pour faire plus intime. Cependant, des proches de Ecma, la Team4, qui avaient pas trop pigé le coup du prototypage, décida de faire évoluer la spécification pour que ça ressemble plus à de l'objet fondé sur les classes comme ces bons langages compilés (miam !) et d'en faire à terme ES4.

Mais holà ! Les puristes se sont levés, la Team5. La Team5 a dit « l'ES, c'est avant tout du prototypage ! De diou ! » Ce qui l'amena à écrire en parallèle la même chose, mais en pas pareil. Cela donna naissance à ES5 que nous avons tous connu et qui vit dans Chrome, Firefox, Safari, Opera, et feu (en fait pas encore) Internet Explorer.

Notre Mic a beau dire qu'il « a compris qu'il faut suivre les spécifications de Ecma », il galère quand même pas mal en se traînant ses versions antérieures incrustées à la cloueuse. Mic, ES4, ES5, etc. : ils ont perdu tout le monde. Les gens inventent des abstractions en JavaScript pour faire du JavaScript qui marche chez tout le monde. Les gars « nous, on aime les prototypes » se sont mis à chier des bibliothèques comme Prototype (qu'on comprenne bien qu'ils aiment les prototypes) tandis que les gars « nous, on aime les classes et le typage fort » se sont mis à chier des transpileurs et truc à la TypeScript parce que selon eux, quand tu croises un type faible mais dynamique, : on se dit que « le gars est louche ». Du coup, maintenant Le gars qui passait par là jette un œil à tout ce beau monde et se dit : « et bah putain... quel merdier ».

ES6 est alors en marche et réconcilie gentiment les gars des prototypes et des classes en prenant le parti suivant : « on va dire que ça marche par prototype, ok ? Mais on va faire en sorte de planquer ça sous de la classe... Malin ! Hein ? ».

Par dessus ça, y a un mec, Joyent, qui se dit que même si JavaScript a besoin d'un hôte pour fonctionner, pourquoi ça serait forcément un navigateur ; l'hôte ? « Et si j'invitais ce bon vieux JavaScript à travailler... sur mon OS ? Que dis-je sur tous les OS ! ». Hop là ! Ni une, ni deux... un peu de HTTP par ci, un peu de lecture/écriture de fichier par là, le tout à la sauce V8 de chez Google et plop : Node.js est né. Du JavaScript côté serveur. À ce niveau, je vous parle pas de Le gars qui passait par là qui, lui, à surement abandonné l'affaire...

En tout cas Joy se laisse pas abattre et met à jour Node.js en intégrant les nouveautés ES6 et plus du moteur V8. Le gars il s'en fout ; il est tout seul (genre le mec, même pas effrayé par IO.js avec lequel il fusionne à « la Dragon Ball Z »). Le gars il fait ce qu'il veut et surtout : il suit scrupuleusement les bons conseils d'Ecma contrairement à un certain Mic. Mic, qui clame haut et fort à présent que « si, si. dans mon Edge, maintenant, je fais tout bien » ; mais sans succès.

Résultat des courses ? La norme HTML5 propulse JavaScript, le code serveur Node.js propulse JavaScript, les nouveautés ES6 propulsent JavaScript, les technos concurrentes utilisant npm propulsent JavaScript, des trucs boiteux comme TypeScript propulsent JavaScript, des trucs swag comme React propulsent JavaScript et des super-héros comme Vue propulsent JavaScript ! Bref, le train roule sacrément vite...

Il est peut-être venu le temps d'essayer de rattraper les wagons si vous êtes restés sur le quai !

Quelles différences entre ES3 et ES5 ?

Avant de continuer plus en avant avec des fonctionnalités ES6 (voir ES7), je vous invite, si vous le souhaitez, à vous attarder sur divers articles mettant en avant les grands concepts du JavaScript pour « enfin » le comprendre et savoir en quoi il différe des autres langages.

Des fondamentaux à ES3

C'est pourquoi je vous invite à commencer par comprendre les bases ES3. Tout d'abord, je vous invite à comprendre comment s'exécute un programme grâce aux contextes d'exécutions. Une fois cela clairement établi, nous entrerons dans le détail en décrivant les éléments qui composent un contexte comme l'objet des variables, la valeur de this ou encore la chaîne des portées. Avec ces nouvelles connaissances, vous serez armé pour bien comprendre tous les types de fonctions et en quoi elles sont toutes des fermetures. Il ne vous restera plus qu'à, pour boucler les fondamentaux ES3, dévorer les vrais tenant et aboutissant d'un langage à typage faible et aux objets basés sur des prototypes. Pour finir, nous lèverons une idée reçu selon laquelle le JavaScript passe ses paramètres par référence en étudiant la stratégie d'évaluation en JavaScript.

Les ajouts ES5

Pour compléter les révisions (ou la découverte) des bases, vous pourrez achever votre remise en ordre ES5 en étudiant la capacité de décrire les propriétés d'objet ainsi que les nouveautés du mode strict (prochainement). Nous finirons les révisions avec le nouveau concept d'environnement lexical (prochainement).

Liste des fonctionnalités ES6+

Le schéma sera le suivant : on tente d'expliquer ce qu'on aurait pu faire en ES3-ES5, on le fait en ES6 et supérieur en constatant les différences et en pointant du doigt la valeur ajoutée.

Constantes const

Constants > Constants

ES6 ajoute un support pour les variables immuables. Sous ce jolie nom se cache simplement le concept de constante : une variable à laquelle aucun nouveau contenu ne peut être assigné après sa déclaration (en utilisant l'opérateur d'affectation à gauche =). À noter que c'est le conteneur et non le contenu qui est immuable, cela signifie qu'un objet qui serait affecté à une variable immuable ne peut pas être remplacé (sa référence ne peut pas être changée), mais son contenu, lui, peut bouger (ajout, modification et suppression de propriétés). Un petit rappel entre, les objets qui sont stockés par référence et les primitives qui sont directement stockées, se trouve ici.

En ES5 : D'après cette description, un des moyens de créer une constante avec ES5 est d'utiliser la fonction Object.defineProperty(). Cette fonction permet d'attribuer à des propriétés d'objet un comportement immuable. Créons ainsi la constante PI.

>  /**
    * `PI` en tant que variable locale ou
    * `window.PI` en tant que variable globale n'existe pas.
    */
   PI;
<· « Uncaught ReferenceError: PI is not defined »
>  /**
    * On va créer une propriété se comportant « comme » une constante.
    * p1 : Affectation de la propriété à l'objet global `window` (navigateur)
    *      ou à l'objet global `global` (node.js).
    * p2 : Définition de la clé de la propriété en tant que l'opérande `"PI"`.
    * p3 : Options attribuant sa valeur avec l'opérande `3.141593`,
    *      expliquant qu'il apparaît lors de l'utilisation d'une boucle et surtout
    *      qu'aucun opérande ne peut-être affecté après sa définition
    */
   Object.defineProperty(window ? window : global, "PI", {
       value:        3.141593,
       enumerable:   true,
       writable:     false
   });
<· Window {…}
>  /* `PI` est maintenant défini et vaut `3.141593`. */
   PI;
<· 3.141593
>  /**
    * Affecter `PI` va effectivement renvoyer
    * l'opérande que vous avez choisi d'affecter.
    */
   PI = "Nombre PI";
<· "Nombre PI"
>  /* Cependant la valeur `PI` n'aura pas bougé d'un iota. */
   PI;
<· 3.141593
>  /**
    * Ce qu'il faut bien comprendre dans notre cas de figure
    * c'est que PI est une variable globale (ou propriété de l'objet global).
    */
   PI === window.PI;
<· true

En ES6 : Le moyen de réellement créer une constante est d'utiliser l'opérateur const.

>  /**
    * `PI` en tant que variable locale ou
    * `window.PI` en tant que variable globale n'existe pas.
    */
   PI;
<· « Uncaught ReferenceError: PI is not defined »
>  /**
    * On va créer une constante `PI` avec la valeur `3.141593`.
    */
   const PI = 3.141593;
<· undefined
>  /* `PI` est maintenant défini et vaut `3.141593`. */
   PI;
<· 3.141593
>  /**
    * Affecter `PI` va cette fois lancer une exception.
    */
   PI = "Nombre PI";
<· « Uncaught TypeError: Assignment to constant variable. »
>  /**
    * Cependant, ce n'est ici pas l'équivalence de notre exemple ES5 car ici
    * `PI` est une variable locale immuable (constante) et
    * non une propriété de l'objet global.
    */
   PI === window.PI;
<· false

En conclusion l'opérateur const qui crée une constante (une variable locale immuable) n'est pas la même chose que la fonction Object.defineProperty() qui crée une propriété d'objet immuable, une sorte de propstante !

Variable à portée limitée let

Scoping > Block-Scoped Variables

Contrairement à beaucoup de langage, en JavaScript ES5, la portée des variables déclarées avec l'opérateur var n'est pas limitée à un bloc d'accolade. Ainsi, si je défini une variable à l'intérieur d'une structure de contrôle de flux comme if, ma variable sera également disponible à l'extérieur. Cela n'est plus le cas si ma variable est déclarée avec let (et également avec const). Cela signifie qu'une variable déclarée dans l'instruction if ou for n'existe pas en dehors.

En ES5 : Voici le comportement induit par les variables déclarées avec l'opérateur var que ce soit entre les accolades mais également dans les boucles.

>  if ("1" == 1) {
       /* Bien qu'elle soit créée dans un bloc... */
       var str = "Hello World";
   }
   /* ...la variable `str` est accessible en dehors de ce bloc. */
   str;
<· "Hello World"
>  if ("1" == 1) {
       /* Seules les fonctions enferment la portée des variables... */
       (function () {
           var str = "Hello World";
       }());
   }
   /* ...et les empêches d'être accessibles de l'extérieur `str`. */
   str;
<· « Uncaught ReferenceError: str is not defined »
>  /**
    * Le fait que les variables ne soient pas limitées aux accolades empêche
    * cette liste de fonction de fonctionner comme on le souhaiterait.
    */
   var callbacks = [];
   for (var i = 0; i < 3; i++) {
       /**
        * Ainsi le problème est que à l'instant ou chaque fonction
        * est assignée dans le tableau `callbacks` à l'indice `i`,
        * `i` vaut la valeur actuelle de la boucle.
        */
       callbacks[i] = function() { return i * 2; };
   }
   /**
    * Cependant, lorsque `callbacks[0]()`, `callbacks[1]()` et `callbacks[2]()` sont exécutées
    * la valeur de `i` est de `3`, ce qui donne pour l'exemple courant au lieu de `0 2 4`...
    */
   callbacks[0]() + " " + callbacks[1]() + " "  + callbacks[2]();
<· 6 6 6
>  /**
    * Pour solutionner le problème il faut faire un instantané
    * de la valeur de `i` en passant `i` en paramètre d'une fonction,
    * ce qui copie sa valeur et la conserve pour la suite puisque
    * `i` est une primitive et que les primitives sont assignées par copie.
    */
   var callbacks = [];
   for (var i = 0; i < 3; i++) {
       (function (i) {
           /* Ainsi le paramètre `i` est figé à sa valeur lors de son assignation. */
           callbacks[i] = function() { return i * 2; };
       }(i));
   }
   /* Et le résultat est cette fois bien celui attendu. */
   callbacks[0]() + " " + callbacks[1]() + " "  + callbacks[2]();
<· 0 2 4

En ES6 : Tous nos problèmes sont résolus ici par ce fameux let. Non seulement celui-ci nous permet de vraiment cloisonner une variable dans un bloc, mais également de créer des boucles sans gérer nous même le cloisonnement de la variable d'itération.

>  if ("1" == 1) {
      /* Cette fois ci une variable créée dans un bloc... */
      let str = "Hello World";
   }
   str;
   /* ...n'est pas accessible en dehors de ce bloc. */
<· « Uncaught ReferenceError: str is not defined »
>  /**
    * Et le fait que les variables soient cette fois limitées aux accolades, et donc
    * à un seul tour de boucle permet à notre liste de fonctionner comme prévue.
    */
   var callbacks = [];
   for (let i = 0; i < 3; i++) {
       /* Grâce à `let`, `i` est un instantané à chaque tour de boucle. */
       callbacks[i] = function() { return i * 2; };
   }
   /* Cela permet à nos fonctions de fonctionner comme souhaité. */
   callbacks[0]() + " " + callbacks[1]() + " "  + callbacks[2]();
<· 0 2 4

En conclusion l'opérateur let est parfait pour permettre des variables locales à un bloc d'être consommées sans impacter le reste du champ lexical. Il permet également de créer des instantanés à chaque tour de boucle pour éviter le cloisonnement.

Fonction à portée limitée {}

Scoping > Block-Scoped Functions

De la même manière qu'il est possible de limiter une variable à un bloc en ES6, il est également possible de limiter une fonction à un bloc. Cela grâce à l'ajout des accolades { et } autour de la zone dont les fonctions doivent être à portée limitée.

En ES5 : Voici le comportement d'une fonction définie puis redéfinie dans un bloc.

>  /* Si je définie une variable dans le flux... */
   function test() { return "external"; }
   /* ...puis que je la redéfinie ensuite dans un block... */
   {
       function test() { return "internal"; }
   }
   /**
    * ... `test()` va retourner `"internal"` puisqu'en JavaScript
    * les blocs ne cloisonnent pas les variables et fonctions.
    */
   test();
<· "internal"
>  function test() { return "external"; }
   /**
    * Il faut faire appel à une fonction anonyme auto-exécutée
    * pour induire un nouveau champ lexical et cloisonner
    * la valeur.
    */
   (function () {
       function test() { return "internal"; }
   }());
   test();
<· "external"

En ES6 : Cependant avec ES6, il est possible de limiter la portée des fonctions déclarées à un bloc et non à une fonction en entourant le tout avec des accolades { et }.

>  /* Si j'englobe les fonctions avec des accolades... */
   {
       function test() { return "external"; }
       /* ...les futures accolades internes seront cloisonnante... */
       {
           function test() { return "internal"; }
       }
       /**
        * ... et ainsi la fonction redéfinie dans un sous bloc
        * ne sera pas prise en compte dans le bloc du dessus.
        */
       test();
   }
<· "external"
>  /**
    * Il faut bien comprendre que ce comportement ne fonctionne qu'avec
    * le mot-clé `function` aussi pour les variables, il faut utiliser `let`
    */
   {
       var withVar = "var external";
       let withLet = "let external";
       {
           var withVar = "var internal";
           let withLet = "let internal";
       }
       withVar + " | " + withLet;
   }
<· "var internal | let external"

En conclusion les fonctions cloisonnées permettent de limiter l'utilisation d'une fonction à un bloc sans créer de nouveau champ lexical. Le fait que ce mécanisme soit limité au mot clé function le rend difficilement appréhendable tout de même.

Valeurs de paramètre par défaut

Extended Parameter Handling > Default Parameter Values

Quand on déclare une fonction en JavaScript, on indique les paramètres qu'on lui passe entre ses parenthèses et... c'est tout. On ne précise pas le type attendu, on ne précise pas de valeur par défaut. Si on souhaite faire cela afin d'indiquer de quel type est une variable ou d'attribuer une valeur autre que undefined en cas de non passage de paramètre, il faut le faire à la main !

Ça c'était avant, car ES6 implémente les paramètres auxquelles ont peut passer des valeurs par défaut en cas d'absence lors de l'appel.

En ES5 : Par défaut, une valeur non passée s'attribut la valeur de type Undefined : undefined.

>  function fn(str, nbr, bool, obj) {
       return {
           str:  str,
           nbr:  nbr,
           bool: bool,
           obj:  obj
       };
   };
   fn("Hello World");
<· Object {str: "Hello World", nbr: undefined, bool: undefined, obj: undefined}

Pour donner plus d'indication sur les valeurs à passer, ou affecter une valeur différente de undefined aux paramètres non passés on fait donc ainsi :

>  function fn(str, nbr, bool, obj) {
       return {
           str:  str || "",
           nbr:  nbr || NaN, // (nbr === 0 || nbr === -0) ? nbr : (nbr || NaN),
           bool: bool || false,
           obj:  obj || null
       };
   };
   fn("Hello World");
<· Object {str: "Hello World", nbr: NaN, bool: false, obj: null}

En ES6 : Cependant avec ES6, il est directement possible d'affecter ses valeurs lors de la déclaration des paramètres.

>  function fn(str = "", nbr = NaN, bool = false, obj = null) {
       return {
           str:  str,
           nbr:  nbr,
           bool: bool,
           obj:  obj
       };
   };
   fn("Hello World");
<· Object {str: "Hello World", nbr: NaN, bool: false, obj: null}

Portée de fonction étendue =>

Arrow Functions > Lexical this

L'utilisation de this est un très vaste chapitre. Nous l'avons déjà traité sur ce blog dans la valeur de this. Ici nous allons étudier son comportement lorsqu'il est utilisé dans un contexte qui le redéfini.

En ES5 : Voici le comportement standard des propriétés associées à this après l'utilisation de fonction utilisant leur propre valeur de this, ce qui est par exemple le cas de setTimeout.

>  /* Nous allons créer une fonction que nous allons utiliser comme constructeur */
   var Character = function (name) {
           /* À sa création, l'objet prendra un nom dans sa propriété `name` */
           this.name = name;
           this.displayNameTwice = function () {
               /* Si bien que lors de l'utilisation de `this.name` */
               /* c'est ce nom qui s'affichera. */
               console.log("0s", this.name);
               /* Mais... */
               setTimeout(function () {
                   /* il ne s'affichera pas... */
                   console.log("1s", this.name);
               /* une seconde plus tard. */
               }, 1000);
           };
       },
       /* Initialisation d'un objet `Character` */
       cloud = new Character("Cloud Strife");

>  /* Appel de la méthode */
   cloud.displayNameTwice();
<· undefined

avec en sortie de console imédiate

   0s Cloud Strife

et une seconde plus tard

   1s

Nous nous apercevons que this.name n'a rien retourné dans le setTimeout car setTimeout possède son propre objet this. Les deux moyens les plus courant pour contourner se comportement normal de JavaScript est

  1. de créer une variable contenant la référence au bon this (ES3) :

    /* ... */
    this.displayNameTwice = function () {
        /* On crée une variable contenant la
        référence vers le `this` souhaité... */
        var self = this;
        console.log("0s", this.name);
        setTimeout(function () {
            /* ...et on utilise cette variable. */
            console.log("1s", self.name);
        }, 1000);
    };
    /* ... */
    

    ou

  2. de lier la valeur qui devra servir de this avec bind (ES5) :

    /* ... */
    this.displayNameTwice = function () {
        console.log("0s", this.name);
        setTimeout(function () {
            console.log("1s", this.name);
        /* On lie lors de la définition de la fonction
        la valeur de `this` qu'elle devra utiliser. */
        }.bind(this), 1000);
    };
    /* ... */
    

En ES6 : Grâce à la nouvelle fonction fléchée =>, il est maintenant possible de conserver le this de la portée appelante quelque soit le cas de figure. La fonction fléchée est donc votre meilleure alliée pour tout ce qui va être de l'ordre de l'utilisation de fonction de rappel (« callback »).

>  var Character = function (name) {
           this.name = name;
           this.displayNameTwice = function () {
               console.log("0s", this.name);
               /* utilisation d'une fonction fléchée
               avec `=>` */
               setTimeout(() => {
                   console.log("1s", this.name);
               }, 1000);
           };
       },
       cloud = new Character("Cloud Strife");

>  cloud.displayNameTwice();
<· undefined

avec en sortie de console imédiate

   0s Cloud Strife

et une seconde plus tard

   1s Cloud Strife

Parfait !

Abréviation d'expression de fonction =>

Arrow Functions > Expression and statement bodies

Partout ou une expression de fonction est utilisable, c'est-à-dire dans toutes expressions valides, une fonction fléchée peut-être utilisée à la place afin d'écrire de manière plus condensé quelque chose de très verbeux. Voici des exemples ES5 transformé en ES6.

En ES5 : Voici ce que l'on pourrait écrire en ES5 :

Trier un tableau ou a est l'entrée courante et b la suivante. En fonction du retour négatif ou positif du résultat, les éléments vont être interverti ou non.

>  [7, 0, 8, 3].sort(function (a, b) {
       return a - b;
   });
<· [0, 3, 7, 8]

Filtrer l'élément courant c du tableau. Si la fonction renvoi true ou garde l'éntrée dans le résultat, sinon, on l'exclue.

>  [7, 0, 8, 3].filter(function (c) {
       if (c % 2 === 0) {
           return true;
       } else {
           return false;
       }
   });
<· [0, 8]

Associer un résultat de corps vide à chaque élément du tableau.

>  [7, 0, 8, 3].map(function () {});
<· [undefined, undefined, undefined, undefined]

Associer un nouvel objet {} à chaque élément du tableau.

>  [7, 0, 8, 3].map(function () {
       return {};
   });
<· [Object, Object, Object, Object]

En ES6 : Et la même chose en ES6 avec la fonction fléchée.

S'il n'y a qu'une ligne dans le corps de la fonction (return a - b), le mot clé return doit être omis, ainsi que les accolades {, } après => (ou le tout conservé, mais l'un ne va pas sans l'autre).

>  [7, 0, 8, 3].sort((a, b) => a - b);
<· [0, 3, 7, 8]

S'il y a un seul paramètre, les parenthèses ( et ) peuvent être omises avant la =>. Si le corps de la fonction contient plus d'une ligne, les accolades { et } restent et le corps de la fonction reste non changé.

>  [7, 0, 8, 3].filter(c => {
       if (c % 2 === 0) {
           return true;
       } else {
           return false;
       }
   });
<· [0, 8]

S'il n'y a pas d'arguments pour la fonction, les parenthèses ( et ) sont obligatoires à gauche de =>. Si le corps de fonction est vide, les accolades { et } sont obligatoire à droite de =>

>  [7, 0, 8, 3].map(() => {});
<· [undefined, undefined, undefined, undefined]

Pour retourner l'objet {} (ou { test: "test" } par exemple) et qu'il ne soit pas confondu avec un corp de fonction vide, il faut des parenthèses ( et ) à droite de =>

>  [7, 0, 8, 3].map(() => ({}));
<· [Object, Object, Object, Object]

En cours de rédaction...