Data

EcmaScript 6 Part 1

Fév 2, 2015

Nicolas Bailly

Bonjour à tous ! Le JavaScript née en 1995 a depuis subi un bon nombre de changements ! Ces modifications du langage accompagnent l’évolution de la norme ECMAScript dont JavaScript est l’une de ses implémentations. La version d’ECMAScript 4 (sans doute trop en avance sur son époque) est malheureusement tombée à l’eau, mais bon nombre de ses évolutions ont été reprises dans la nouvelle version de la norme : ECMAScript 6 ! Ce billet a pour but de vous éclairer sur les principales nouveautés apportées par cette sixième version.

Introduction

Je ne vais pas vous refaire l’intégralité de l’histoire du JavaScript. En bref, en 1995 c’est les débuts d’internet et donc des navigateurs. Deux d’entre eux domine le marché : Netscape et Internet explorer. Javascript fut créé en 1995 par l’un des programmeurs de Netscape : Brendan Eich. Microsoft ne veut pas rater le coche et implémente sa propre version du langage. L’année d’après Netscape soumet à l’ECMA (un organisme de standardisation) sa version. Les spécifications sont validées en 1997, naquit ainsi la norme que l’on appelle l’ECMAScript. Tous les navigateurs font évoluer leur moteur qui interprétera votre code JavaScript à partir de cette norme. Depuis 1997 elle a régulièrement été mise à jour et nous allons entrer dans l’air de la sixième version ! Elle sera adoptée officiellement en juin 2015, mais dès maintenant les navigateurs travail pour supporter progressivement les nouvelles fonctionnalités. Apportant un grand lot de nouveauté, l’article sera scindé en quatre parties afin de décrire précisément les apports de ces évolutions.

Déstructuration

La déstructuration va vous permettre d’extraire des valeurs d’un objet ou d’un tableau. La syntaxe utilisée est similaire à celle pour construire un nouveau tableau ou pour déclarer un literal object. Voici un exemple :

/*Exemple avec un tableau*/
var tab = ["nb1", "nb2", "nb3"];

// sans déstructuration
var nb1 = tab[0];
var nb2 = tab[1];
var nb3 = tab[2];

// avec 
var [nb1 , nb2 , nb3] = tab;

/*Exemple avec un objet*/
var myObject = {nb: 42, myBool: true};
var {nb, myBool} = myObject;

console.log(nb); // 42
console.log(myBool); // true 

// Changement du nom des variables
var {nb: nbTest, myBool: myBoolTest} = myObject;

console.log(nbTest); // 42
console.log(myBoolTest); // true

Les fonctions

A. Paramètre par défaut

Voici enfin l’arrivée des paramètres avec une valeur par défaut. Avec ES5 les paramètres par défaut c’était ça :

function doCalcul(nb1, nb2, operator){
    nb2= nb2|| 4;
    operator= operator|| '+';
    //...
}

Lorsqu’on débarque du C#, Java, C++, … Cela ne semble pas du tout intuitif. Voici ce que ça donne en ES6 :

function doCalcul(nb1, nb2=4, operator='+'){
    //...
}

Beaucoup plus compréhensible et surtout en lisant la signature de la fonction on peut directement voir les paramètres par défaut.

B. Rest parameter

En JavaScript on peut passer autant d’argument que l’on souhaite à une fonction, sans pour autant tous les définir dans la signature. On les récupère à l’aide de la variable arguments. Voici ce que ça donne :

function doCalcul(nb1, nb2, operator){
    console.log(arguments.length);// 5
    console.log(arguments[3]);// 8
    console.log(arguments[4]);// 6
    //...
}

doCalcul(4, 5, '+', 8, 6);

La variable arguments est un tableau contenant l’ensemble des paramètres soumis lors de l’appel à la fonction. Donc pas de possibilités de regrouper les paramètres supplémentaires. Voici maintenant ce que ça donne avec ES6 :

function doCalcul(nb1, nb2, operator, ...otherNumber){
    console.log(otherNumber.length);// 2
    console.log(otherNumber[0]);// 8
    console.log(otherNumber[1]);// 6
    //...
}

doCalcul(4, 5, '+', 8, 6);

Dans cet exemple la variable ‘otherNumber’ regroupe l’ensemble des paramètres supplémentaires. La seule restriction dans cette notation est qu’un paramètre rest ne peut être déclaré qu’en dernier dans la signature d’une fonction (tout comme les paramètres ayant une valeur par défaut).

C. Opérateur de décomposition (spread operator)

Cet opérateur est intimement lié au paramètre rest et à la fonction apply. Apply permet d’appeler une fonction en lui passant un context (this) et un ensemble de paramètres (si vous voulez en savoir plus cliquez ici). Tandis que le paramètre rest vous permet de passer un ensemble de paramètres indépendants combinés dans un tableau, l’opérateur de décomposition (ou spread operator) vous permet de soumettre un tableau à une fonction où chaque item du tableau sera traité comme un paramètre à part entière. Noté ‘…’, il est utilisé principalement dans trois cas:

  • Appel de fonction : comme décrit plus haut l’opérateur de décomposition vous permet de passer un tableau de valeur à une fonction. Ce tableau sera ‘splité’ et chacun de ses éléments sera passés en paramètre de la fonction.
    var tabVal = [-5, 13, 20];
    var tabVal2 = [35, 39];
    Math.max(...tabVal, ...tabVal2, 42); //42
    
  • Déclaration de tableaux : l’opérateur est très utile pour inclure un tableau précédemment déclaré dans un nouveau tableau.
    //Déclaration de tableau
    var tab1 = ['chaine1', 'chaine2', 'chaine3'];
    var tab2 = ['foo', ...tab1, 'bar', 'test'];
    
    //Méthode push de l'objet Array
    var arr1 = [0, 1, 2];
    var arr2 = [3, 4, 5];
    arr1.push(...arr2);
    
  • Déclaration de variable et instanciation : dans le cas de déclaration de variable, l’opérateur est utilisé conjointement à la déstructuration. L’opérateur s’utilise avec l’opérateur new pour passer un tableau de paramètre à un constructeur.
    //Avec déstructuration
    [a, b, ...c] = [1, 2, 3, 4, 5]
    
    //Avec l'opérateur new
    new Date(...[2015, 2, 20]);
    

D. La propriété ‘name’

Il est bien souvent difficile d’identifier une fonction en JavaScript. De plus les fonctions anonymes ne facilite pas la tâche. C’est pour cela qu’avec la norme ES6 toutes les fonctions possèdent une propriété par défaut appelé name qui retourne le nom de la fonction. Voici un exemple :

function doCalcul(nb1, nb2, operator){
    //...
}

var doMoreCalcul = function(nb1, nb2, operator) {
    //...
}
console.log(doCalcul.name);// doCalcul
console.log(doMoreCalcul .name);// doMoreCalcul 

E. Fonction ‘fléché’

Déjà connu sous le nom de lambda en C#, la fonction ‘fléché’ a notamment un atout majeur : elle conserve le context du parent (this). Avec ES5 on stocke le context parent dans une variable (qui la plupart du temps pour non ‘self’ ou ‘that’) pour pouvoir l’utiliser dans une sous-fonction. On utilise également la méthode bind décrite ici. Conserver le contexte parent vous permet par exemple de le réutiliser dans une callback. Voici un exemple de fonction ‘fléché’ :

//Avec ES5
function Runner() {
  var self = this;
  self.dist = 0;
  setInterval(function run() {
    self.dist++;
  }, 1000);
}
var r = new Runner();

//Avec ES6
function Runner() {
  this.dist = 0;
  setInterval(run() => {
    this.dist++;
  }, 1000);
}
var r = new Runner();

Déjà faisable avec ES5 c’est en partie du sucre syntaxique qui va permettre d’améliorer la lisibilité du code. Les fonctions fléchés sont bien des objets de type function, mais elles ont des restrictions par rapport aux fonctions ‘classiques’ :

-elles ne peuvent pas être utilisées en tant que constructeur
-le contexte parent est toujours ‘binder’, elles n’ont donc pas leur propre contexte. Le contexte binder est non modifiable.
-elles ne possèdent ni constructeur, ni prototype. Il n’est donc pas possible des les appeler avec le mot-clé new et puisqu’elles n’ont pas de prototype, impossible d’étendre leurs fonctionnalités.

F. Fonction niveau block

Pour clarifier les choses voici ce qu’on appelle une fonction niveau block :

if (true) {
    function myFunction() {
        // ...
    }
}

C’est tout simplement une fonction déclarée à l’intérieur d’un block. Avec ES3 cela provoquait une erreur sur certain navigateur, d’autre l’interprétait mais le résultat variait d’un navigateur à l’autre. En ES5 avec l’arrivée du 'strict mode', cela provoque systématiquement une erreur, le cas est enfin normalisé. Avec ES6 ? Cela ne pose plus de problème, il est possible de déclarer une fonction directement à l’intérieur d’un block ! Celle-ci ne sera accessible que depuis ce block. Voici un exemple :

'use strict';

if (true) {
    function myFunction() {
        console.log('42');
    }
    myFunction(); //42
}

myFunction(); //undefined

Si je vous parle de hoisting en JavaScript, cela vous dit-il quelque chose ? Non ? Alors lisez d’abords cela : ici. Avec l’utilisation du 'strict mode', dans l’exemple ci-dessus la définition de la fonction est hisser en haut du block dans lequel elle est déclarée. À l’extérieur de ce block elle n’existe plus et un appel à celle-ci retourne donc undefined. Sans l’utilisation du 'strict mode' le comportement n’est pas le même :

if (true) {
    function myFunction() {
        console.log('42');
    }
    myFunction(); //42
}
myFunction(); //42

Ici la définition de la fonction est hissée dans le scope global et est donc accessible même à l’extérieur du block (dans lequel elle est déclarée). Avec ES6, d’autres nouveautés bouleverse la portée, voyez ce qui suit !

La portée des variables

Vous le savez surement déjà, en JavaScript la portée d’une variable est équivalente au block fonction dans le lequel elle est déclarée. Si une variable n’est pas déclarée avec le mot-clé var, ou si elle est déclarée en dehors de toute fonction, alors elle appartient au scope global et est donc rattachée à l’objet window. Une erreur courante en JavaScript et de déclarer une variable dans un block if puis la même dans un block else. Or comme dit plus haut la portée d’une déclaration n’est pas lié à un block quelconque, seulement au block fonction. Avec ES6 l’arrivé du mot clé let change tout ! Il est maintenant possible de déclarer une variable dans un block et que celle-ci soit défini uniquement à l’intérieur de ce block. Voici un exemple :

if(true) {
     let a = 6;
     console.log(a); //6
}
console.log(a); // a is not defined

Autre différence avec le mot clé var, il n’est pas possible de définir une même variable dans un block :

//Sans let
if(true) {
    var a = 4;
    var a = 5;
    console.log(a); // 5
}

//Avec let
if(true) {
    let a = 4;
    let a = 5; //Throw error
    console.log(a);
}

Autre particularité, lorsqu’une variable est défini avec le mot clé let, sa déclaration n’est pas hissée (hoisted) en haut du block dans lequel elle est défini. La portée de la variable commence donc à partir de sa déclaration jusqu’à la fin du block qui la contient :

if(true) {
    console.log(a); // Throw error
    console.log(b); // undefined
    let a = 6;
    var b = 6;
}

Un cas particulier est l’utilisation de let dans les boucles. Voici un exemple avec ES5 :

 var tabFn = [];

 for (var i=0; i < 10; i++) {
     tabFn.push(function() { console.log(i); });
 }

 tabFn.forEach(function(fn) {
     fn(); // Affiche 10, dix fois
 });

Le problème est que la variable i est déclarée une fois. Chaque fonction crée ici une closure qui reçoit une référence pointant sur i, lorsque l’une d’entre elles modifient la variable elle est modifiée pour toutes les autres. Pour pallier ce genre de problème on utilise les IIFEs (immediately-invoked function expressions), afin de forcer une copie de la variable est non un passage par référence :

 var tabFn = [];

 for (var i=0; i < 10; i++) {
     tabFn.push((function(val) {
         return function() {
             console.log(val);
         }
     }(i)));
 }

 tabFn.forEach(function(fn) {
     fn(); // On a bien le résultat attendu, 1 puis 2 puis 3, ...
 });

Avec le mot clé let on obtient le même résultat tout en conservant un code épuré. À chaque tour de boucle une nouvelle variable est créée, initialisé à partir de la variable de même nom créé à la précédente itération. Voici le résultat :

 var tabFn= [];

 for (let i=0; i < 10; i++) {
     tabFn.push(function() { console.log(i); });
 }

 tabFn.forEach(function(fn) {
     fn(); // On a bien le résultat attendu, 1 puis 2 puis 3, ...
 })

Dernière chose sur le mot clé let, sachez qu’il est possible de l’utiliser dans le scope global, mais cela est fortement déconseiller. En effet il n’est pas possible de définir deux fois la même variable au sein du même block lorsqu’on utilise let. Utilisé dans le scope global, on risque rapidement des collisions de nom.

Le but de ce mot clé est de remplacer progressivement var. Si vous programmez déjà en ES6 préféré son utilisation et utilisez var seulement pour des scripts devant être rétro compatible.

Les contantes

Il est enfin possible de déclarer des constantes non modifiables à l’aide du nouveau mot clé const. La constante définit doit impérativement avoir une valeur définit lors de sa déclaration. Tout comme avec let, la portée d’une constantes et lié au block dans lequel elle est déclarée. Evidemment modifier une constante après l’avoir défini provoque une erreur. Voici un exemple :

const MY_CONST= 42;

MY_CONST = 36; // Throw error

if(true) {
 const MY_NEW_CONST= 42;
 console.log(MY_NEW_CONST); // 42
}

console.log(MY_NEW_CONST); // MY_NEW_CONST is not defined

Conclusion

Dans cette première partie, nous avons pu voir les énormes changements qu’apporte cette sixième édition d’EcmaScript. Les nouveautés vont révolutionner l’écriture du JavaScript, rendant le langage plus intuitif pour les développeurs habitués au langage tel que le C#, le Java,… De même cela devrait rendre le code plus lisible et plus clair. Dans cette première partie j’ai fait le tour de beaucoup de nouveautés qui pourraient être affiliées à du sucre syntaxique, mais dans la seconde partie nous verrons les modules où ECMAScript 6 modifie en profondeur leurs fonctionnent.

0 commentaires

Soumettre un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Découvrez nos autres articles

Aller au contenu principal