Dans le précédent article j’ai évoqué de façon assez globale les possibilités de Javascript. On va maintenant entrer un peu plus dans le détail.
Les variables
On a vu précédemment qu’on dispose des mots-clés var, let et const. Voyons un peu comment les utiliser de façon judicieuse :
- var : la variable est créée dans le scope de la fonction et « hoisted » (j’explique ça plus loin)
- let : la variable est créée dans le scope du bloc
- const : c’est comme let mais avec une valeur qui ne change pas
Dans cette histoire la notion de scope est fondamentale, voyons un exemple :
var s = "Albert"; for(var s in ["un", "deux"]); console.log(s); // Maintenant s = 1 s = "Albert"; for(let s in ["un", "deux"]); console.log(s); // On a encore s = "Albert"
Avec let la variable reste confinée au scope du for et ne change pas la valeur de la variable créée précédemment avec var.
D’autre part une fois qu’une variable a été déclarée avec let elle ne peut plus être redéclarée, alors qu’avec var c’est possible.
Une dernière différence tient à la notion de « hoisted ». Une variable déclarée avec var existe en amont de sa déclaration, contrairement à let :
console.log(a) // undefined var a = 1 console.log(b) // error let b = 1
Le fonctionnement de const est exactement le même que let mais on ne peut pas changer la valeur.
En général on conseille de ne plus utiliser var, d’utiliser const par défaut et let si la valeur doit changer.
Les nombres
ECMAScript possède deux types numériques natifs : Number et BigInt :
- Number : nombre flottant sur 64 bits, même pour représenter un entier
- BigInt : nombre entier
BigInt permet de représenter des entiers plus grands que Number mais il est encore très mal pris en charge et doit être évité. Pour les entiers on doit donc se contenter de Number mais avec un intervalle entre -9007199254740992 et +9007199254740992 ça laisse quand même de la marge !
Number possède des propriétés et méthodes :
let s = 100 console.log(Number.isInteger(s)); // true console.log(Number.isFinite(1 / 0)); // false
Les chaînes de caractères
On dispose de l’objet global String pour les chaînes de caractères. Dans Javascript on peut imaginer les chaînes de caractères comme une liste de caractères. D’ailleurs on peut retrouver un caractère particulier à partir de son indice ainsi :
console.log("Ma chaîne à moi"[8]) // "e"
On retrouve dans Javascript les caractères d’échappement habituels comme le saut de ligne :
let s = "je suis\n" + "un texte" console.log(s) // "je suis // un texte"
On voit également ici qu’on concatène les chaînes avec l’opérateur +.
On peut utiliser les guillemets simples ou doubles en jouant avec eux et en échappant quand il le faut :
let s = 'Je suis "texte" qui se \'charge\' facilement' console.log(s) // Je suis "texte" qui se 'charge' facilement
L’objet String offre de nombreuses méthodes :
let s = 'Une petite phrase' console.log(s.charAt(5)); // "e" console.log(s.includes('petite')); // true console.log(s.indexOf('p')); // 4 console.log(s.lastIndexOf('p')); // 11 console.log(s.slice(4, 10)); // "petite" console.log(s.split(' ')); // ["Une", "petite", "phrase"] console.log(s.substring(0, 2)); // "Un"
Avec ES6 sont apparus les littéraux de gabarits qui permettent d’intégrer des expressions et représenter des textes sur plusieurs lignes :
let s = `Ligne 1 Ligne 2`; console.log(s) // Ligne 1 // ligne 2 let nom = "Durand" console.log(`Le nom est ${nom}`) // "Le nom est Durand"
Les fonctions
Dans Javascript les fonctions sont un type comme les autres, on peut les assigner à des variables, les passer en paramètres, les retourner dans une fonction… On a vu dans l’article précédent qu’il y a deux façons classiques de les créer :
- une déclaration avec le mot-clé function
- l’assignation à une variable
Il n’y a pas de grande différence entre ces deux approches. La déclaration permet le chargement de la fonction avant toute exécution de code, ce qui permet de l’utiliser en amont.
Les fonctions fléchées
Je vous ai aussi parlé rapidement des fonctions fléchées qui sont apparue avec ES6 et qui sont vraiment élégantes :
const carre = a => a ** 2 console.log(carre(4)) // 16
Le fait d’avoir un seul paramètre évite l’utilisation de parenthèses, mais si on en a plus on n’y échappe pas :
const somme = (a, b) => a + b console.log(somme(4, 8)) // 12
On voit aussi qu’on évite la création d’un bloc et donc l’utilisation de return. Mais si vous avez plusieurs instructions alors il faut un bloc et donc aussi return :
const somme = (a, b) => { console.log(`Je vais ajouter ${a} et ${b}`) return a + b } console.log(somme(4, 8)) // 12
Les fonctions anonymes
Il y a beaucoup de fantaisie dans Javascript et en voici une célèbre, les fonctions anonymes. En voilà une :
function() { console.log('Je suis anonyme !') }
Elle n’a pas de nom. Alors on peut se demander à quoi ça sert parce qu’on ne peut jamais l’appeler….
On peut quand même l’exécuter immédiatement comme ça :
(function() { console.log('Je suis anonyme !') })()
Le seul avantage qu’on peut y trouver c’est d’isoler du code du contexte global…
Mais les fonctions anonymes servent dans un autre cas plus répandu. Un truc un peu dépaysant de Javascript est son fonctionnement asynchrone. Il est non bloquant : on peut lancer une tâche qui prend du temps, continuer à exécuter le programme, et revenir à la tâche qu’on avait lancée quand elle est terminée :
console.log("Un") setTimeout(function () { console.log("Deux") }, 500) console.log("Trois")
La fonction setTimeout est asynchrone, son premier paramètre est un callback, c’est à dire une fonction qui sera appelée à la fin de la tâche, ici juste une attente de 500 ms. On voit qu’ici l’utilisation d’une fonction anonyme est justifiée. C’est quelque chose qui est très utilisé dans Javascript mais qui oblige souvent à une certaine gymnastique mentale.
Les promesses
L’alternative aux callback ce sont les promesses. Tout le monde sait ce qu’est une promesse : on dit qu’on fera quelque chose plus tard… ou pas…
L’avantage des promesses par rapport aux callbacks c’est que ce qui est retourné est un objet et peut être traité comme les autres éléments de Javascript. En gros des callbacks améliorés.
On crée une promesse avec le constructeur Promise :
let promesse = new Promise(function (resolve, reject) { // On accomplit une tâche if ( Tout s'est bien déroulé ) { resolve(value) } else { reject(reason) } })
On a une fonction comme argument (appelée exécuteur) qui elle-même a deux fonctions comme arguments :
- resolve : action à accomplir si tout s’est bien passé,
- reject : action à accomplir dans le cas contraire.
On utilise la promesse ainsi :
promesse.then(resultat => { console.log(result) // Ca s'est bien passé ! }, err => { console.log(err) // Il y a une erreur ! })
La méthode then a deux arguments, encore des fonctions de retour, une pour le succès et une pour l’échec. Les deux sont optionnels.
Une promesse peut être dans l’un de ces 4 états :
- pending : en attente,
- fulfilled : réussie,
- rejected : échouée,
- settled : acquittée (réussie ou échouée).
Un bon exemple d’utilisation des promesses est celui de l’API Fetch des navigateurs. J’avais donné un exemple dans cet article :
<!doctype html> <html lang="fr"> <head> <meta charset="utf-8"> <title>Les régions de France</title> </head> <body> <h1>Les régions de France</h1> <ul></ul> <script> window.addEventListener('DOMContentLoaded', (e) => { fetch('https://geo.api.gouv.fr/regions') .then(response => { if (response.ok) { response.json().then(regions => { const ul = document.querySelector('ul') for (const r of regions) { const newLI = document.createElement('li') newLI.appendChild(document.createTextNode(r.nom)) ul.appendChild(newLI) } }) } else { console.error('Réponse du serveur : ' + response.status) } }); }); </script> </body> </html>
On a deux promesses : une avec fetch() et une avec json().
Avec ce résultat sur la page :
Les paramètres
Avec ES6 on bénéficie de nouvelles facilités pour les paramètres des fonctions. On peut par exemple prévoir une valeur par défaut comme dans la plupart des autres langages :
const ajoute = (a, b = 3) => a + b console.log(ajoute(2)); // 5
Rien n’empêche de prévoir une fonction comme valeur par défaut.
Lorsqu’on utilise plus de paramètres que prévu en appelant une fonction on utilisait classiquement arguments pour récupérer les valeurs. Mais avec ES6 on dispose du paramètre du reste :
const ajoute = (a, ...valeurs) => { let total = a for (const i of valeurs) { total += i } return total } console.log(ajoute(1, 2, 3, 4)) // 10
Le paramètre du reste transforme les paramètres restants en valeurs dans un tableau.
On a aussi l’inverse avec l’opérateur de décomposition qui transforme un tableau en paramètres :
console.log(Math.max(...[-10, 6, 12, 4])) // 12
Les objets
Dans Javascript les objets sont les structure de base dont toutes les autres découlent comme par exemple les tableaux. Pour définir un objet on utilise des accolades :
const identite = {nom: "Durand", prenom: "Albert"} console.log(identite.nom) // "Durand" identite.nom = "Dupont" console.log(identite.nom) // "Dupont"
On peut comme on le voit créer des propriétés, les récupérer ou les changer en séparant le nom de l’objet et de la propriété avec un point.
On peut itérer dans un objet avec la syntaxe for in :
const identite = {nom: "Durand", prenom: "Albert"} for (const k in identite) { console.log(identite[k]) } // "Durand" // "Albert"
Au passage on voit qu’en plus de la syntaxe avec un point on peut aussi utiliser les crochets pour accéder à une propriété.
Mais on aime pas trop laisser les propriétés si facilement accessible, en général on met en place des accesseurs :
const identite = { _nom: "Durand", get nom() { return this._nom }, set nom(value) { this._nom = value; } } console.log(identite.nom) // "Durand" identite.nom = "Dupont" console.log(identite.nom) // "Dupont"
Ainsi on peut mieux contrôler les propriétés.
On peut aussi ajouter des méthodes dans l’objet :
const identite = { nom: "Durand", age: 20, majeur: function () { return this.age > 18 } } console.log(identite.majeur()) // true
On s’est longtemps contentés de ça mais avec l’arrivée d’ES6 on dispose maintenant de vraies classes pour définir des objets :
class Identite { constructor(nom, age) { this.nom = nom this.age = age } majeur() { return this.age > 18 } } const i = new Identite("Durand", 20) console.log(i.majeur()) // true
Sous le capot on a toujours des fonctions mais c’est quand même plus élégant !
On peut utiliser l’héritage :
class Identite { constructor(nom, age) { this.nom = nom this.age = age } } class Employe extends Identite { constructor(nom, age, metier) { super(nom, age) this.metier = metier } } const i = new Employe("Durand", 20, "agriculteur") console.log(i.nom) // "Durand" console.log(i.metier) // "agriculteur"
On se rend compte que peu à peu Javascript rentre dans le rang des langages classiques !
Conclusion
On a un peu plus avancé dans la connaissance de la version moderne de Javascript. Dans le prochain article on verre de près les événements.