I. Historique de JavaScript▲
Ces nouveaux ajouts au langage sont appelés ECMAScript 6, ou encore ES6, ou ES2015+.
Depuis sa création en 1995, JavaScript a lentement évolué. De nouveaux ajouts ont été faits au fil des années. ECMAScript est arrivé en 1997 pour orienter JavaScript, et a publié des versions telles qu’ES3, ES5, ES6, etc.
Comme vous pouvez le voir, il y a des trous des 10 et 6 ans entre ES3, ES5 et ES6. Le nouveau modèle est de faire de petits ajouts croissants chaque année, au lieu de changements massifs en un coup comme avec ES6.
II. Compatibilité avec les navigateurs▲
Tous les navigateurs et environnements récents supportent déjà ES6 !
Chrome, Edge, Firefox, Safari, Node et bien d’autres ont déjà intégré la plupart des fonctionnalités de JavaScript ES6. Tout ce que vous aurez donc à faire pour l’utiliser sera de suivre ce tutoriel.
C’est parti pour la prise en main d’ECMAScript 6 !
III. Fonctionnalités principales d’ES6▲
Vous pouvez tester ces bouts de code dans la console de votre navigateur ! (F12)
Donc ne me croyez pas sur parole et essayez tous les exemples ES5 et ES6. Allons-y !
III-A. Variables à portée de bloc▲
Avec ES6, nous sommes passé·e·s de la déclaration de variables avec var à l’utilisation de let et const.
Quel était le problème de var ?
Le problème de var est que la variable persiste dans d’autres blocs de code comme des boucles for ou des if.
Pour test(false) vous vous attendez à obtenir « externe », mais non, vous avez undefined.
Pourquoi ?
Eh bien, parce que même si le bloc if n’est pas exécuté, l’expression var x en ligne 4 est hissée.
Hissage de variable (variable hoisting) :
- var a la fonction pour portée. La variable est disponible dans toute la fonction, même avant d’être déclarée ;
- les déclarations sont hissées, vous permettant d’utiliser une variable avant sa déclaration ;
- les initialisations ne sont pas hissées. Si vous utilisez var, déclarez toujours vos variables avant le reste ;
- après application des règles de hissage, il est plus facile de comprendre ce qu’il s’est passé :
ECMAScript 2015 vient à la rescousse :
Remplacer var par let fait fonctionner le code comme attendu. Si le bloc if n’est pas appelé, la variable x n’est pas hissée hors du bloc.
Hissage de let et « zone morte temporaire » :
- avec ES6, let hisse la variable en haut du bloc au lieu du haut de la fonction comme ES5 ;
- cependant, utiliser la variable dans ledit bloc avant sa déclaration donnera une ReferenceError ;
- let a le bloc pour portée. On ne peut pas l’utiliser avant sa déclaration ;
- la « zone morte temporaire » est la zone entre le début du bloc et la déclaration de la variable.
III-A-1. IIFE▲
Prenons un exemple avant d’expliquer les IIFE (Immediately-Invoked Function Expression, ou fonction immédiatement appelée) :
2.
3.
4.
5.
{
var privee =
1
;
}
console.log
(
privee);
// 1
Comme vous le voyez, privee persiste hors du bloc. Il faut utiliser une IIFEImmediately-Invoked Function Expression pour le contenir :
Si vous regardez jQuery, Lodash ou d’autres projets open source vous remarquerez leur utilisation d’IIFE pour éviter de polluer l’environnement global, et ne définir que des globales comme _, $ ou jQuery.
Avec ES6 c’est bien plus propre, plus besoin d’IIFE quand on peut juste utiliser un bloc avec let :
2.
3.
4.
5.
{
let privee =
1
;
}
console.log
(
privee);
// ReferenceError
III-A-2. const▲
Vous pouvez aussi utiliser const si vous ne voulez pas changer une variable (et ainsi en faire une constante).
En bref, laissez tomber var au profit de let et const.
- Utilisez const pour toutes vos références ; évitez var.
- Si vous voulez pouvoir modifier les références, utilisez let au lieu de const.
III-B. Modèles de libellés (template literals)▲
Plus besoin de concaténation avec les modèles de libellés. Voyez plutôt :
2.
3.
var prenom =
"Adrian"
,
nom =
"Mejia"
;
console.log
(
"Vous vous appelez "
+
prenom +
" "
+
nom +
"."
);
On peut désormais utiliser l’accent grave (`) (se trouve sur la touche 7 sur les claviers AZERTY) avec des interpolations :
2.
3.
const prenom =
"Adrian"
,
nom =
"Mejia"
;
console.log
(
`Vous vous appelez
${prenom}
${nom}
.`
);
NDT L’espace réservé par ${} n’est pas limité aux références, il peut contenir n’importe quel code JS, y compris des opérations, des appels de fonctions, et même d’autres modèles de libellés.
III-C. Chaînes de caractères multilignes▲
Plus besoin de concaténer des chaînes avec \n comme ceci :
2.
3.
4.
5.
6.
7.
8.
9.
var modele =
"<li *ngFor='let tache of tachesAFaire' [ngClass]='{completed: tache.estFaite}' >
\n
"
+
" <div class='vue'>
\n
"
+
" <input class='bascule' type='checkbox' [checked]='tache.estFaite'>
\n
"
+
" <label></label>
\n
"
+
" <button class='detruire'></button>
\n
"
+
" </div>
\n
"
+
" <input class='modifier' value=''>
\n
"
+
"</li>"
;
console.log
(
modele);
Les modèles de libellé d’ES6 résolvent une fois de plus le problème :
2.
3.
4.
5.
6.
7.
8.
9.
const modele =
`<li *ngFor="let tache of tachesAFaire" [ngClass]="{completed: tache.estFaite}" >
<div class="vue">
<input class="bascule" type="checkbox" [checked]="tache.estFaite">
<label></label>
<button class="detruire"></button>
</div>
<input class="modifier" value="">
</li>`
;
console.log
(
modele);
Ces deux morceaux de code font exactement la même chose.
III-D. Affectation par décomposition (destructuring assignment)▲
ES6 est très pratique et concis. Voyez ces exemples :
Obtenir les éléments d’un tableau
2.
3.
4.
var tableau =
[
1
,
2
,
3
,
4
],
premier =
tableau[
0
],
troisieme =
tableau[
2
];
console.log
(
premier,
troisieme);
// 1 3
Avec ES6 :
2.
3.
const tableau =
[
1
,
2
,
3
,
4
],
[
premier,
,
troisieme]
=
tableau;
console.log
(
premier,
troisieme);
// 1 3
Échanger les valeurs
Avec ES6 :
2.
3.
4.
5.
let a =
1
,
b =
2
;
[
a,
b]
=
[
b,
a];
console.log
(
a,
b);
// 2 1
Décomposer pour retourner plusieurs valeurs
Ligne 3, vous pourriez aussi retourner un tableau comme ceci :
return [
gauche,
droite,
haut,
bas];
Mais après, il faut retenir l’ordre des valeurs retournées.
var gauche =
data[
0
],
bas =
data[
3
];
Avec ES6, l’appelant ne prend que les données dont il a besoin :
Note : ligne 3, nous voyons une autre fonctionnalité d’ES6 : il est possible de compresser { gauche: gauche } en { gauche }. C’est pas formidable ?
Décomposer pour faire correspondre les paramètres
La même chose, mais plus concise :
Correspondance en profondeur
Idem :
Ce qu’on appelle aussi la décomposition d’objet (object destructuring).
Bonne pratique :
- utilisez la décomposition de tableau pour en récupérer des éléments ou échanger des variables. Cela vous épargne la création de variables temporaires ;
- n’utilisez pas la décomposition de tableaux pour retourner de multiples valeurs, utilisez plutôt lé décomposition d’objet.
III-E. Classes et objets▲
Avec ECMAScript 6, nous passons des « fonctions constructrices » aux « classes ».
En JavaScript, tout objet a un prototype, qui est un autre objet. Tous les objets JavaScript héritent leurs méthodes et attributs de leur prototype.
En ES5, nous faisions la Programmation Orientée Objet (POO) avec des fonctions constructrices utilisées comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
var Animal = (
function (
) {
function Constructeur
(
name
) {
this.
name
=
name
;
}
Constructeur.
prototype.
speak =
function parler
(
) {
console.log
(
this.
name
+
' makes a noise.'
);
};
return Constructeur;
}
)(
);
var animal =
new Animal
(
"animal"
);
animal.parler
(
);
// animal fait du bruit.
ES6 nous donne un peu de sucre syntaxique. On peut faire la même chose avec moins d’expressions génériques et de nouveaux mots-clés tels que class et constructor. Remarquez notamment comment nous définissons la méthode parle() :
Comme vous le voyez, les deux styles (ES5/6) donnent le même résultat et sont utilisés de la même manière.
Bonnes pratiques :
- utilisez toujours la syntaxe avec class et évitez de changer manuellement le prototype. Pourquoi ? Parce qu’ainsi le code est davantage concis et facile à comprendre ;
- évitez d’avoir un constructeur vide. Les classes ont un constructeur par défaut si aucun n’est défini.
III-F. Héritage▲
Travaillons notre classe Animal. Mettons que nous voulons l’étendre et définir une classe Lion.
En ES5, il faut bidouiller encore plus le prototype :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
var Lion = (
function (
) {
function Constructeur
(
name
){
Animal.call
(
this,
name
);
}
// prototypal inheritance
Constructeur.
prototype =
Object.create
(
Animal.
prototype);
Constructeur.
prototype.
constructor
=
Animal;
Constructeur.
prototype.
parler =
function parler
(
) {
Animal.
prototype.
parler.call
(
this);
console.log
(
this.
name
+
' rugit'
);
};
return MyConstructor;
}
)(
);
var lion =
new Lion
(
'Simba'
);
lion.parler
(
);
// Simba fait du bruit.
// Simba rugit.
Pour expliquer un peu :
- ligne 2, nous appelons explicitement le constructeur d’Animal en passant les paramètres ;
- lignes 6-7, nous assignons à Lion le prototype d’Animal ;
- ligne 10, nous appelons la méthode parle de la classe parente Animal.
En ES6, nous avons les mots-clés extends et super.
Constatez l’immense gain en lisibilité de ce code ES6 comparé à l’ES5. Yeah !
Bonne pratique :
utilisez la fonctionnalité d’héritage intégrée avec extends.
III-G. Fonctions fléchées▲
ES6 n’a pas supprimé les expressions de fonctions, mais en a ajouté une nouvelle appelée « fonctions fléchées ».
En ES5, on avait quelques soucis avec this :
On doit utiliser une référence temporaire à this pour l’utiliser au sein d’une fonction, ou d’utiliser bind.
En ES6, on peut utiliser une fonction fléchée !
NDT En principe, la syntaxe est (
paramètres…) =>
{
code }
,
mais il est possible de se passer des parenthèses s’il n’y a qu’un paramètre, et de se passer des accolades s’il n’y a qu’une instruction.
(
) =>
console.log
(
"Fonction fléchée sans paramètre."
);
(
chaine) =>
{
console.log
(
chaine);
}
chaine =>
console.log
(
chaine) // idem, mais plus concis
(
prm1,
prm2) =>
{
console.log
(
prm1);
console.log
(
prm2);
}
// Plusieurs paramètres et instructions, pas le choix que de mettre parenthèses et accolades
NDT Les fonctions fléchées ne redéfinissant pas this, il ne faut pas les utiliser comme méthodes pour les objets.
III-H. Promesses natives▲
Nous sommes passé·e·s de l’enfer des rappels (callback hell) aux promesses (promises).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
function afficheApresDelai
(
chaine,
delai,
fait){
setTimeout
(
function(
){
fait
(
chaine);
},
delai);
}
afficheApresDelai
(
"Salut "
,
2e3
,
function(
resultat){
console.log
(
resultat);
// rappel imbriqué (nested callback)
afficheApresDelai
(
resultat +
"lecteur·rice"
,
2e3
,
function(
result){
console.log
(
result);
}
);
}
);
Nous avons là une fonction qui reçoit un rappel à exécuter une fois que c’est fait. Nous devons l’exécuter deux fois l’une après l’autre. C’est pourquoi nous avons rappelé afficheApresDelai une seconde fois dans la fonction de rappel.
Cela peut vite devenir un sacré bazar si vous avez besoin d’un 3e ou 4e rappel. Voyons un peu comment s’en sortir avec les promesses :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
function afficheApresDelai
(
chaine,
delai){
return new Promise((
resout,
rejette) =>
{
setTimeout
(
function(
){
resout
(
chaine);
},
delai);
}
);
}
afficheApresDelai
(
"Salut "
,
2e3
).then
(
resultat =>
{
console.log
(
resultat);
return afficheApresDelai
(
`
${resultat}
lecteur·rice`
,
2e3
);
}
).then
(
resultat =>
{
console.log
(
resultat);
}
);
Comme vous le voyez, avec les promesses nous pouvons utiliser then pour faire quelque chose après qu’une autre fonction est terminée. Plus besoin d’imbriquer les rappels.
III-I. For…of▲
Nous sommes passé·e·s de forEach à for…of :
Le for…of d’ES6 nous permet de faire des itérations :
NDT Conseil : oubliez forEach. La raison est simple : forEach est une fonction, et appeler une fonction à répétition a un coût. Préférez l’utilisation de for…of pour de meilleures performances.
III-J. Paramètres par défaut▲
Alors que nous devions vérifier qu’un paramètre avait bien été défini, nous pouvons désormais lui assigner une valeur par défaut. Avez-vous déjà fait quelque chose de ce genre ?
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
function point
(
x,
y,
estMarque){
x =
x ||
0
;
y =
typeof(
y) ===
"undefined"
?
-
1
:
y;
isFlag =
typeof(
estMarque) ===
'undefined'
?
true
:
estMarque;
console.log
(
x,
y,
estMarque);
}
point
(
0
,
0
) // 0 0 true
point
(
0
,
0
,
false) // 0 0 false
point
(
1
) // 1 -1 true
point
(
) // 0 -1 true
Tous ces typeof(…) === « undefined »… Heureusement, ES6 nous facilite la tâche :
III-K. Paramètres du reste (rest parameters)▲
Des arguments, nous sommes passé·e·s aux paramètres du reste et à la syntaxe de décomposition (spread operator).
En ES5, obtenir un nombre arbitraire d’arguments est assez pataud :
Nous pouvons faire la même chose avec l’opérateur de reste (…) :
III-L. Syntaxe de décomposition (spread operator)▲
Nous sommes passé·e·s d’apply() à la syntaxe de décomposition, avec … à la rescousse :
Rappel : nous utilisons apply() pour convertir un tableau en list d’arguments. Par exemple, Math.max() prend une liste de paramètres, mais si nous avons un tableau nous pouvons utiliser apply() pour que cela fonctionne :
Comme nous venons de le voir, nous pouvons utiliser apply pour passer des tableaux comme des listes d’arguments :
En ES6, nous pouvons utiliser la syntaxe de décomposition :
Math.max
(...[
2
,
100
,
1
,
6
,
43
]
) // 100
Cela permet également de remplacer concat :
En ES6 :
NDT La syntaxe de décomposition a été ajoutée pour les objets, mais seulement dans le standard ES2018 (aussi appelé ES9), qui est très récent au moment d’écrire cette note et devrait donc être évité pendant encore quelque temps.
Cherchez Object Rest/Spread Properties pour plus de détails.
IV. Conclusion▲
JavaScript a subi beaucoup de changements. Ce tutoriel couvre la plupart des principales fonctionnalités que tout·e développeu·r·se JavaScript devrait connaître. Aussi, nous avons vu les bonnes pratiques à adopter pour rendre votre code plus concis et facile à comprendre.