Avant de s’attaquer au cœur du sujet, il est primordial de voir quelques bases sur la syntaxe JavaScript moderne.
Voyons donc ensemble les nouveautés en JS, et ce qu’elles peuvent nous apporter pour un code plus simple et moderne.
Si vous avez un doute sur le support d’une fonctionnalité (puisqu’il s’agit ici de choses assez récentes), le site Can I use permet de voir rapidement les navigateurs qui la supportent ou non.
Les variables et constantes
Si vous avez déjà fait un peu de développement, vous êtes probablement déjà familiers avec le concept des variables.
Pour faire simple, ce sont des noms que vous utilisez pour stocker des données (qu’il s’agisse de chaînes de caractère, de nombres, de booléens ou même d’objets complexes).
var
pour les variables à grande portée
Historiquement, en JavaScript on utilisait le mot-clé var
pour déclarer une variable :
var bonjour // Déclaration de la variable
bonjour = 'Bonjour tout le monde' // Initialisation de la variable
var age = 42 // Déclaration et initialisation en une seule instruction
Le problème de ces variables est leur portée : elles sont valides pour l’ensemble de la fonction qui les contient. Il devient donc difficile de les déclarer dans un bloc qui n’est pas une fonction, comme une boucle :
for (var i=1; i<=10; i++) {
var j = i // 1, 2, 3 … 10
}
j // 10
i // 11
On le voit ici : la variable j
a été déclarée dans la boucle, mais est accessible à l’extérieur et risque par exemple de rentrer en conflit avec une autre variable j
.
Pour cette raison, il existe maintenant un mot-clé plus pratique qui remplace peu à peu var
…
let
pour les variables locales
Tout comme var
, le mot-clé let
permet de déclarer une variable. La différence est sa portée, qui est limitée au bloc qui la contient :
for (let i=1; i<=10; i++) {
let j = i // 1, 2, 3 … 10
}
j // undefined
i // undefined
Les variables ne sont ainsi accessibles que dans le bloc (ici délimité par des accolades) qui les contient (ainsi que leurs enfants), sans écraser des variables d’un bloc supérieur.
const
pour les valeurs fixes
Et si vous voulez enregistrer une valeur qui ne changera jamais ? Vous savez, une constante !
Et bien, il existe le mot-clé const
qui fonctionne à peu près comme let
, sauf qu’il ne vous laisse pas modifier la valeur vers laquelle le nom pointe.
const AGE_MINIMUM = 7
const AGE_MAXIMUM = 77
AGE_MINIMUM-- // Invalide, la valeur ne peut pas être remplacée
AGE_MAXIMUM = 100 // Invalide, la valeur ne peut pas être remplacée
Attention : vous ne pouvez pas modifier le pointeur (la référence mémoire), mais vous pouvez modifier l’objet contenu dans votre constante :
const ANIMAUX = ['🐬', '🐺', '🦊']
ANIMAUX.push('🦙', '🦌') // 5
// Le tableau contient maintenant 5 éléments sans erreur, car il s'agit d'un objet et non d'une valeur primaire
Si vous voulez en apprendre plus sur les déclarations en JS, n’hésitez pas à lire la page MDN sur le sujet.
Les littéraux de gabarits
Également appelés templates literals pour les intimes, il s’agit d’une nouvelle façon de créer des chaînes de caractère, avec plusieurs avantages à la clé…
Les sauts de ligne
À la différence des chaînes classiques, délimitées par des quotes (simples ou doubles), vous pouvez sauter une ligne sans problème dans un littéral de gabarit :
let singleQuotes = '
' // SyntaxError: '' string literal contains an unescaped line break
let doubleQuotes = "
" // SyntaxError: "" string literal contains an unescaped line break
let templateLiteral = `
` // OK
L’interpolation de données
Fini les concaténations sans fin ! Si vous voulez injecter une variable (ou une constante) dans un littéral de gabarit, il existe une syntaxe spécifique pour ne pas avoir à concaténer, en utilisant le marqueur ${}
autour de votre valeur :
const NUMERO = '007'
let message = `Bonjour, Agent ${NUMERO}` // "Bonjour, Agent 007"
Et ça fonctionne aussi avec une expression, un appel de fonction ou une propriété d’objet :
const auteur = {
pseudo: 'viki53',
espece: '🐬',
tutoriels: 4
}
function nomEspece(code) {
switch (code) {
case '🐬':
return 'un dauphin'
case '🐺':
return 'un loup'
case '🦌':
return 'un caribou'
case '🦊':
return 'un renard'
default:
return 'un animal inconnu'
}
}
let presentation = `Ceci est le ${auteur.tutoriels + 1}ᵉ tutoriel de ${auteur.pseudo}, qui est ${nomEspece(auteur.espece)}` // "Ceci est le 5ᵉ tutoriel de viki53, qui est un dauphin"
Les valeurs sont évaluées au moment de la déclaration, pas à chaque fois que vous faites appel à votre chaîne. Si vous changez une valeur après avoir déclaré la chaîne il faudra la redéfinir.
let langage = 'JavaScript'
let message = `Le meilleur langage pour le Web est ${langage}`
langage = 'Python'
message // "Le meilleur langage pour le Web est le JavaScript"
Les étiquettes ou tags
En plus de pouvoir interpoler des variables, vous pouvez aussi récupérer chaque morceau des chaînes pour modifier la chaîne finale via une étiquette :
const nom = 'viki53'
const metier = 'développeur'
Ces étiquettes sont des fonctions, qui doivent répondre à une définition précise : le premier paramètre est un Array
contenant les morceaux bruts de la chaîne (en dehors des marqueurs d’interpolation), les autres paramètres contenant les valeurs à injecter :
function majuscules(morceaux, ...valeurs) {
let str = ''
for (let i in morceaux) {
str += morceaux[i] + (valeurs[i] || '').toUpperCase()
}
return str
}
On peut ensuite appliquer le tag à notre template string :
const direBonjour = majuscules`Bonjour, je m'appelle ${nom} et je suis ${metier}` // "Bonjour, je m'appelle VIKI53 et je suis DÉVELOPPEUR"
Les fonctions fléchées
Les arrow functions sont des fonctions comme les autres, à un détail près : elles n’ont pas de this
spécifique. Elles permettent également une syntaxe plus courte, pratique pour déclarer des fonctions anonymes.
function classique() {
console.dir(this) // body
}
document.body.addEventListener('click', classique)
const flechee = () => {
console.dir(this) // Window
}
document.body.addEventListener('click', flechee)
Si vous avez un doute sur l’usage de this
ou ce qu’il définit, vous pouvez trouver une explication sur le MDN.
Ça peut paraître abstrait pour le moment, mais vous verrez en construisant l’application que c’est très utile
À noter que les fonctions (fléchées ou non) n’ont pas forcément besoin d’un nom, elles peuvent être anonymes :
document.documentElement.addEventListener('scroll', event => { console.dir(event) }, { once: true, passive: true })
Les modules
Une des nouveautés du JavaScript moderne est l’apparition des modules, qui permettent de structurer son code en séparant chaque fonctionnalité ou composant indépendamment du reste : chaque module gère son aspect métier sans se soucier des autres.
Chaque module est exécuté dans un contexte différent : vous ne risquez donc pas d’avoir des conflits de variables entre deux fichiers. Un module peut donc, en règle générale, être transposé d’un projet à l’autre directement sans risque.
Avant de pouvoir charger un module, il faut d’abord en exporter un pour le rendre accessible aux autres :
L’export permet d’exposer une valeur afin que les autres modules puissent y accéder. Sans export la valeur reste interne au module, les autres ne peuvent donc pas y accéder directement.
Vous pouvez également exporter un ensemble de variables et fonctions :
Pour utiliser un module, il faut déclarer au navigateur que l’on veut charger notre fichier dans ce contexte, via un attribut HTML :
<script src="./js/mon-app.js" type="module"></script>
Notre JavaScript pourra alors à son tour charger d’autres modules, via une syntaxe spécifique :
import maFonction from './ma-fonction.js' // Charge l'export par défaut (`default`) du module sous le nom `maFonction`
import * as monModule from './un-module.js' // Charge l'ensemble du module
import { auteur, nomEspece as emojiVersNomEspece } from './un-module.js' // Importe une partie du module, renomme un des imports
À noter que les chemins pour les imports sont calculés à partir du dossier courant.
Les class
Pour se rapprocher d’autres langages, le JS moderne a introduit les class
pour définir des objets, avec des propriétés et des méthodes selon une syntaxe classique :
class Personne {
constructor(nom) {
this.nom = nom
}
direBonjour() {
return `Bonjour, je m'appelle ${this.nom}`
}
}
Il ne s’agit pas réellement d’objets, mais de prototypes (la syntaxe est convertie en prototype par le moteur JavaScript), comme dans les versions précédentes de JavaScript. Il s’agit surtout d’une couche syntaxique pour clarifier/simplifier le code.
const auteur = new Personne('viki53')
auteur.direBonjour() // "Bonjour, je m'appelle viki53"
Vous pouvez aussi utiliser l’héritage pour étendre une class
parente (et une seule, pour le moment) :
class EtreVivant {
constructor() {}
get espece() {
return this._espece
}
}
class Dauphin extends EtreVivant {
constructor() {
super()
this._espece = '🐬'
}
}
const viki53 = new Dauphin()
viki53.espece // "🐬"
N’oubliez pas de vérifier le support des fonctionnalités que vous utilisez, par exemple via Can I use.
Par exemple pour les template literals, IE11 ne les gère pas. Edge 13, ainsi que Safari 9.1 et supérieurs, les gèrent (avec un bug connu sur Safari 12).
Maintenant que vous savez (presque) tout sur la syntaxe, on va pouvoir passer aux choses sérieuses !