JavaScript

JavaScript est un langage incontournable en développement web, il se distingue par sa dynamique, la manipulation du DOM, la gestion de l'asynchrone et son interaction fluide avec HTML/CSS.

Informations

Date de publication :

Date de modification :

Catégories : javascript, html, css, api

Auteur : Photo de profil de l'auteur de la documentation meezyr

Sommaire

Bannière documentation JavaScript

JavaScript est un langage de programmation interprété, faiblement ou dynamiquement typé, fondé sur les prototypes, orienté objet et multi-paradigmes. Il est largement utilisé pour le développement web, permettant d'ajouter des fonctionnalités interactives aux sites internet. Dans cette documentation, nous allons explorer les principaux concepts et fonctionnalités de JavaScript. Pour en savoir plus, consultez la documentation Mozilla.

1. Les bases de JavaScript

1.1 Babel et la compatibilité des navigateurs

Babel est un outil permettant d'unifier les différentes versions de JavaScript pour les rendre compatibles avec tous les navigateurs. Il transforme le code écrit dans des versions récentes de JavaScript en une version compatible avec les anciens navigateurs.

1.2 Variables

JavaScript propose trois façons de créer des variables : var, let et const. Il est recommandé de ne plus utiliser var et de privilégier const pour les valeurs immuables et let pour les valeurs mutables déclarées à l'intérieur d'un bloc.

1.3 Hoisting

Le hoisting (hissage en français) est un comportement particulier de JavaScript où les déclarations de variables et de fonctions sont automatiquement remontées en haut du code lorsque l'interpréteur parse (parcourt et analyse) le code.

1.4 Types de données

JavaScript utilise sept types primitifs, qui sont les suivants :

  • Les booléens
  • Les nombres
  • Les chaînes de caractères
  • Les symboles
  • Null
  • undefined
  • BigInt

1.5 Opérateurs de précédence

Pour comprendre la précédence des opérateurs en JavaScript, vous pouvez consulter la liste complète sur la documentation Mozilla.

1.6 Méthodes de conversion de types

JavaScript permet de convertir les valeurs d'un type à un autre à l'aide de méthodes telles que :

String(1);  // "1"
Number("22"); // 22
Boolean("true"); // true

1.7 Opérateur de comparaison

JavaScript effectue des comparaisons en utilisant des opérateurs stricts (===), comparant à la fois la valeur et le type, et des opérateurs non stricts (==), comparant uniquement les valeurs après conversion de type.

1.8 Références

Vous entendrez souvent qu'en JavaScript, les primitives sont passées par valeur et les objets par référence. Vous entendrez également que les primitives, contrairement aux objets, sont immuables (elles ne peuvent pas être modifiées), par exemple :

const a = 1;
let b = a;
b = 2;
console.log(a, b); // 1 2
const obj = {a: 1};
const obj2 = obj;
obj2.a = 2;
console.log(obj); // {a: 2}

2. Structures de Contrôle

2.1 Opérateur conditionnel

L'opérateur conditionnel est le seul opérateur ternaire en JavaScript :

4 === 3 ? "Oui" : "Non"; // "Non"

2.2 L'instruction switch

L'instruction switch permet d'évaluer une expression et d'exécuter une ou plusieurs instructions suivants sa valeur en déclarant des cas (case) :

const pays = 'France';
switch (pays) {
     case 'France':
     case 'Allemagne':
     case 'Suède':
     case 'Espagne':
         console.log('Tu es européen !');
         break;
     case 'Italie': {
         let nationalite = 'Tu es italien !';
         console.log(nationalite);
         break;
     }
     default:
         console.log("Tu n’es pas européen.");
}

2.3 Boucles for, while et do...while

La boucle for répète un bloc d'instructions jusqu'à ce qu'un test ne soit plus vérifié :

let texte = "";
for (let i = 0; i < 3; i++) {
    if (i === 1) {
        continue; // Stop de l'itération actuel et l'exécution est reprise à l'itération suivante
    }
    if (texte === "texte") {
        break; // sort de la boucle
    }
    texte += i;
}
console.log(texte); // 02

Les instructions while et do...while permettent de répéter une ou plusieurs instructions tant qu'un test est vérifié :

let i = 0;
while (i < 3) {
      i++;
      console.log(i); // 1, 2, 3
}
let j = 0;
do {
    j++;
    console.log(j); // 1, 2, 3
} while (j < 3);

3. Manipulation nombres

3.1 Conversions en nombre

La fonction parseFloat() permet d'analyser (parser) une chaîne de caractères afin de la transformer en nombre flottant. La fonction parseInt() permet d'analyser (parser) une chaîne de caractères afin de la transformer en entier dans la base spécifiée :

parseFloat(" 32.21frefz"); // 32.21
parseInt(" 32.21frefz", 10); // 32

3.2 Objet Number

L'objet natif Number possède plusieurs méthodes, en voici quelques une :

  • Number.parseFloat()
  • Number.parseInt()
  • Number.isFinite()
  • Number.isInteger()
  • Number.isNaN()
  • Number.isSafeInteger()
  • (12312).toExponential(2); // "1.23e+4"
  • (42.658486).toFixed(1); // 42.7
  • (1.23456).toPrecision(3); // 1.23

3.4 Objet Math

Math est un objet natif JavaScript pour gérer les mathématiques facilement, il a de nombreuses propriétés et de nombreuses méthodes, voici un exemple : 

Math.PI; // Pi
Math.floor(Math.random() * 10); // Obtenir un entier entre 0 et 9

Pour en savoir plus, consultez la documentation Mozilla.

4. Manipulation de chaînes de caractères

4.1 Échappements

Les échappements permettent de sauter des lignes et des pages ou de faire des tabulations, comme ceci :

  • \n : Permet le saut de ligne line feed.
  • \t : Permet la tabulation.
  • \f : Permet le saut de page.
  • \' : Permet l’échappement guillemet simple.
  • \" : Permet l’échappement guillemet double.

4.2 Littéraux

Les littéraux de chaînes de caractères permettent d'inclure, tous les espaces, y compris les sauts de ligne et l'indentation dans le résultat, voici un exemple : 

`<h1>Attention !</h1>
<p>Votre abonnement numéro ${numeroAbonnement} va prochainement se terminer. Il ne vous reste que ${joursAbonnement+1} jour${joursAbonnement > 1 ? `s`}.<p>`

4.3 Nombre de caractères

La propriété length permet d'obtenir le nombre d'unités de codage UTF-16 d’une chaîne de caractères :

console.log('Salut'.length); // 5
'Salut'[1]; // a

4.4 Autres méthodes

De multiples méthodes sont disponibles pour le traitement de chaines de caractères (string) sur la documentation

5. Objets en JavaScript

5.1 Déclaration d’un objet

Un objet est un ensemble de paires clé / valeur (appelée propriété), voici un exemple d’objet :

const employe1 = {
    age: 23,
    salaire: 25000,
    fonction: 'vendeur'
};

Une propriété est comme une variable attachée à un objet :

const monObjet = {};
monObjet["prop1"] = 42;
monObjet.prop1 = 43;
console.log(monObjet); // {prop1: 43}

Un raccourci syntaxique existe et s'utilise lorsque le nom souhaité pour la propriété d'un objet correspond au nom d'une variable, comme dans l’exemple suivant :

const a = 1;
const b = 42;
const c = {a: 2};
const monObjet = {
    a,
    b,
    c,
}; // {a: 1, b: 42, c: {a: 2}}
const prop1 = "Exemple";
const prop2 = "concaténation";
const monObjet = {
    [`${prop1} ${prop2}`]: "pratique !",
};

5.2 Décomposition d’un objet

L'affectation par décomposition (destructuring) permet d'extraire des données d'un objet :

const unObjet = { toto: 1, tutu: 2 };
const { toto, tutu } = unObjet;
console.log(toto, tutu); // 1, 2

5.3 Opérateur Rest

L'opérateur rest dont la syntaxe est il permet d'affecter toutes les propriétés de l'objet que nous n'avons pas explicitement affectées à des variables à un objet, il peut être utiliser pour enlever des propriétés d'un objet :

const { a, b, ...monReste } = {a: 1, b: 2, c: 3, d: 4};
console.log(a, b, monReste); // 1, 2, {c: 3, d: 4}

5.4 Tester l’existence d’une valeur

Pour tester l'existence et la valeur d'une propriété, il existe notamment l'opérateur in qui renvoie un booléen suivant qu'une propriété appartient à l'objet donné, comme dans l’exemple suivant :

const monObj = {a: 1, b:2};
if ("a" in monObj) {} // true
if ("b" in monObj) {} // false

La méthode hasOwnProperty() retourne un booléen indiquant si l'objet a la propriété passée en paramètre, par exemple :

monObj.hasOwnProperty("a"); // true

5.5 Supprimer une propriété

Pour supprimer la propriété d'un objet il suffit d'utiliser l'opérateur delete, comme dans l’exemple suivant :

delete monObj.a;

Pour indiquer qu'une propriété n'a pas de valeur, il suffit d'utiliser null comme dans le code suivant :

monObj.a = null;

5.6 Fusionner

Pour copier un objet de manière superficielle, on peut utiliser Object.assign() ou l'opérateur Spread …. Pour fusionner des objets, on peut utiliser la méthode assign(), comme dans l’exemple suivant : 

Object.assign(objetCible, objetACopier1, objetACopier2...)

Pour fusionner des objets avec l'opérateur Spread …, comme dans l’exemple suivant :

const monObj = {a: 1};
const monObj2 = {b: 2};
const copie = {...monObj, ...monObj2}; // {a: 2, b: 2}

5.7 Comparer

Les objets sont comparés par référence (une référence à un objet est son adresse dans la mémoire), par exemple : 

const obj = {a: 1};
const obj2 = obj;
console.log(obj === obj2); // true car même référence
const obj3 = {a: 1};
console.log(obj === obj3); // false

5.8 Itérer

L'instruction for...in, cette instruction permet d'itérer, c'est-à-dire de parcourir les unes après les autres, l'ensemble des propriétés d'un objet :

const obj = {a: 1, b: 2, c: 4};
for (const prop in obj) {
    console.log(prop);
} // a // b // c

5.9 Méthodes

Il y a plusieurs méthodes utiles sur un object :

  • Object.values(obj) : Renvoie un tableau contenant toutes les valeurs des propriétés non héritées d'un objet.
  • Object.keys(obj) : Renvoie un tableau contenant toutes les clés des propriétés non héritées d'un objet.
  • Object.entries(obj) : Renvoie un tableau contenant des tableaux dans lesquels vous trouverez en première position la clé et en seconde position la valeur des propriétés non héritées d'un objet.
  • Object.getOwnPropertyDescriptor() : Permet d'accéder aux attributs d'une propriété d'un objet.

5.10 JSON

Le format JSON (pour JavaScript Object Notation) est le format le plus utilisé dans le Web :

const obj = {a: {}, b: 42, c: true, d: 'test'};

La méthode JSON.stringify() permet de convertir tout objet ou primitive JavaScript au format JSON :

JSON.stringify(obj); // {"a":{},"b":42,"c":true,"d":"test"}
JSON.stringify({ a: 1, b: 2 }, null, 3); // Le troisième argument qui permet de gérer les espaces pour l'affichage
// {
//   "a": 1,
//   "b": 2
// }

La méthode JSON.parse() permet d'analyser du format JSON pour reconstruire un objet JavaScript :

JSON.parse({"a":{},"b":42,"c":true,"d":"test"}); // {a: {}, b: 42, c: true, d: 'test'}

Pour obtenir une copie profonde d'un objet qui ne partage aucune référence en commun avec l'objet source il faut utiliser une petite astuce :

JSON.parse(JSON.stringify(objet))

6. Fonctions et Expressions

6.1 Déclaration de fonctions

Pour déclarer une fonction en JavaScript, il y a 2 façons de faire : 

function nom2(param1, param2) {}
const additionne = function (nombre1, nombre2) {}

On peut déclarer une valeur par défaut à une fonction en JavaScript, comme dans l’exemple suivant :

function test(param1, param2 = 42, param3) {
    param1 = param1 || 41;
    console.log(param1, param2);
}
test(); // 41 42 undefined

6.2 Opérateur Rest

L'opérateur Rest … dans les paramètres permet de récupérer l'ensemble des arguments passé dans la fonction : 

function calculer(operateur, ...nombres) {
    // Calcul
    return resultat;
}
const values = calculer('+', 42, 22, 12, 23); // ['+', 42, 22, 12, 23]

Pour définir la valeur retournée par une fonction, il faut utiliser le mot clé return, on ne peut retourner qu'une seule valeur et arrête son exécution au moment du return.

6.3 Fonctions fléchées

Les fonctions fléchées sont très concises, flexibles et il est facile de raisonner avec elles, par exemple :

(param1, param2) => {
 // instructions
}
param1 => { // avec un seul paramètre, il est possible d'enlever les parenthèses l'entourant
   // instructions
   return valeur;
}
const declaration = param1 => instruction; // Il est possible d'omettre les accolades et même le mot clé return
declaration(params);

Pour retourner un objet littéral lorsque vous utilisez une fonction fléchée avec le retour implicite il vous faudra utiliser des parenthèses :

const fonction = () => ({ a: 1 });
fonction(); // { a: 1 }

Une fonction de rappel (callback) est une fonction passée en argument à une autre fonction et qui est exécutée dans cette dernière, par exemple :

test('test 01', params, data => console.log(data));

Une fermeture (ou closure) est une fonction qui utilise des identifiants de la portée parente, et ce même si la fonction parente n'existe plus, par exemple :

function fonction1() {
    const prenom = "Jean";
    return () => console.log(prenom);
}
const fonction2 = fonction1();
fonction2(); // "Jean"

7. Contexte d'Exécution et Portée (Scope)

7.1 Environnement lexical et contexte d'exécution

L'environnement lexical est l'endroit où sont stockés les identifiants, c'est-à-dire les noms des variables et des fonctions, et leurs valeurs (qui sont des références pour les objets et donc les fonctions).

Le contexte d'exécution est une construction permettant de suivre l'exécution du code. Le JavaScript est single threaded et synchrone. La portée est l'ensemble des identifiants accessibles dans un contexte d'exécution. La chaîne de portée (ou scope chain) est le processus que le moteur JavaScript suit pour chercher les identifiants lors de l'exécution.

Les IIFE (Immediately Invoked Function Expression) sont des fonctions JavaScript qui sont exécutées dès qu'elles sont définies (permet d'éviter les collisions d'identifiants avec le code parent), par exemple :

(function () {
    var a = "Paul";
})();
a; // ReferenceError

7.2 Utilisation de l'instruction "use strict"

Le mode strict active le mode restrictif de JavaScript, qui se comporte différemment dans certains cas. C'est une amélioration recommandée du langage :

"use strict"; // Au début du script

7.3 Gestion explicite du mot clé "this"

Le mot clé this en JavaScript permet d'accéder à l'objet représentant le contexte d'exécution. Attention, la valeur de this dépend du contexte d'exécution et non pas de l'environnement lexical.

Pour définir explicitement la valeur de this, il suffit d'utiliser call() ou apply() et de passer en premier argument la valeur de this. Pour lier la valeur de this, la méthode bind() permet de créer un clone d'une fonction en liant définitivement la valeur de this à l'argument passé en premier paramètre.

8. Tableaux en JavaScript

8.1 Création de tableaux

Les tableaux (array en anglais) sont des objets spécifiques qui sont des listes ordonnées d'éléments, par exemple :

const tableau = [1, 'chat', true, {}];
const tableau = Array.from('chat'); // ["c", "h", "a", "t"]
Array.isArray(tableau);

Déclarer des valeurs par défaut à un tableau, comme dans l’exemple suivant :

const [a = 0, f = 2] = liste;
console.log(a, f); // 1 2

8.2 Méthodes de manipulation de tableaux

Pour réinitialiser un tableau sans modifier sa référence :

tableau.length = 0;
console.log(tableau); // []

Pour ajouter ou modifier un élément et que le tableau conserve sa référence, on peut :

tableau[0] = 1;
console.log(tableau[0]); // 1

L'affectation par décomposition de tableaux :

const liste = [1, 2, 3, 4];
const [a, b] = liste;
console.log(b); // 2

Sauter un ou plusieurs éléments lors de l'affectation d'un tableau :

const [, , a, b] = liste;
console.log(a, b); // 3 4

Inverser les valeurs de deux variables d'un tableau :

let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

Utiliser l'opérateur Rest … pour affecter le reste des éléments d'un tableau à une variable :

const [a, ...leReste] = liste;
console.log(a, leReste); // 1 [2, 3, 4]

Pour ajouter des éléments à un tableau :

const tableau = [1, 2, 3];
tableau.push(4); // [1, 2, 3, 4]
const copie = [0, ...tableau, 4]; // [0, 1, 2, 3, 4]
tableau.splice(1, 0, 3, 4); // [1, 3, 4, 2, 3]
tableau.unshift(4) // [4, 1, 2, 3]

Pour supprimer des éléments dans un tableau :

tableau.shift(); // [2, 3]
tableau.pop(); // [1, 2]
tableau.splice(-2, 1); // [1, 3]

Pour trouver des éléments dans un tableau :

  • tableau.indexOf(3); // 2 : Renvoie l'index du premier élément trouvé.
  • tableau.lastIndexOf(3); // 2 : Renvoie l'index du dernier élément trouvé.
  • tableau.includes(3); // true : Permet de vérifier si un tableau contient une valeur.
  • tableau.find(el => el > 2); // 3 : Renvoie le premier élément trouvé dans le tableau qui vérifie la condition.
  • tableau.findIndex(el => el > 2); // 2 : Renvoie l'index du premier élément trouvé dans le tableau qui vérifie la condition.

Pour fusionner des tableaux, utiliser le code suivant :

const tableau1 = [1, 2, 3];
const tableau2 = [4, 5];
const tableau3 = tableau1.concat(tableau2); // [1, 2, 3, 4, 5]
const tableau3 = [...tableau1, ...tableau2]; // [1, 2, 3, 4, 5]

Pour trier un tableau, utiliser le code suivant :

const tableau = ['Z', 'b', 'k', 'Y', 'c'];
tableau.sort(); // ["Y", "Z", "b", "c", "k"]
const tableau2 = ['Château', 'élève', 'Baron', 'antre', 'étage', 'espiègle'];
tableau2.sort((a, b) => a.localeCompare(b, 'fr')); // ["antre", "Baron", "Château", "élève", "espiègle", "étage"]
tableau.reverse(); // ['c', 'Y', 'k', 'b', 'Z']

Pour itérer sur un tableau, utiliser le code suivant :

const tableau = [1, 2, 3, 4, 5];
for (let i = 0; i < tableau.length; i++) {
    console.log(`${i}: ${tableau[i]}`); 
} // 0: 1 ...
tableau.forEach((el, i) => console.log(`${i}: ${el}`)); // 0: 1 ...
for (const valeur of tableau) {
    console.log(valeur);
} // 1 ...

8.3 Programmation fonctionnelle et méthodes

La programmation fonctionnelle est un paradigme de programmation qui consiste à concevoir ses programmes comme un ensemble de fonctions mathématiques que l'on compose entre elles, par exemple :

const tableau = [1, 2, 3];
const tableau2 = tableau.map(el => el + el); // [2, 4, 6]
const tableau3 = tableau.map(Number).filter(el => el > 1); // [2, 3]

La méthode reduce() est utilisée le plus souvent pour réduire une liste à une seule valeur, par exemple :

const tableau = [1, 2, 3, 4];
const total = tableau.reduce((acc, curr) => acc + curr); // 10

La méthode flat() permet de créer et de retourner un nouveau tableau contenant tous les éléments des tableaux imbriqués :

const tableau = [1, 2, [3, 4], [[5], [[6],7]]];
const tableau2 = tableau.flat(2); // [1, 2, 3, 4, 5, [6], 7]

La méthode flatMap() permet de combiner la méthode map() et la méthode flat(1) de manière optimisée pour la performance :

const test = [1, 3, 5].flatMap(el => [el, el + 1]); // [1, 2, 3, 4, 5, 6]

La méthode every() permet d'effectuer un test pour l'ensemble des éléments d'un tableau :

const tableau = [2, 5, 8, 4, 12];
const resultat = tableau.every(el => el < 10); // false

La méthode some() permet de vérifier qu'au moins un élément d'un tableau passe un test :

const resultat = tableau.some(el => el < 10); // true

9. Exports, Imports et Modules

9.1 Exportation et importation d'identifiants

On peut exporter des fonctions et des variables, cela signifie que vous rendez disponible la fonction ou la variable pour être importée dans un ou plusieurs modules (donc fichiers), cela grâce au mot clé export devant la déclaration :

export const tableau = [1, 2, 3];
export function Compter() {}
export class User {}
// ou
const tableau = [1, 2, 3];
function Compter() {}
class User {}
export { tableau, Compter as CompterUsers, User } // On peut renommer les identifiants

L'export par défaut permet de déclarer un unique export qui sera utilisé lorsque nous ne précisons pas ce que nous souhaitons importer dans les modules de destination. Il suffit d'utiliser export default devant la déclaration :

export default function Compter() {}

Pour importer des identifiants qui ont été exportés explicitement, il suffit d'utiliser des accolades puis d'utiliser tous les identifiants que vous souhaitez voir importés, par exemple :

import { tableau as tableauNombre , Compter, User } from "chemin-vers-module"; // On peut renommer les identifiants

On peut également utiliser import * as et un identifiant pour importer l'intégralité des exports d'un module :

import * as monModule from 'chemin-vers-module'; // Il est recommandé de mettre l'extension du fichier
monModule.tableau;
monModule.Compter;
monModule.User;

On peut importer ce qui est exporté par défaut et qui n'a donc pas de nom, il suffit d'utiliser un identifiant sans accolade qui contiendra l'export par défaut :

import unIdentifiant from 'chemin-vers-module';

Pour réexporter des modules, il est possible d'importer et d'immédiatement réexporter des identifiants :

export { monExport } from 'chemin-du-module'; // Dans index.js dans le dossier du module
import { monExport } from 'chemin-du-dossier-index'; // Dans app.js par exemple

9.2 Imports dynamiques

Les imports dynamiques avec import() permettent d'importer des modules sous certaines conditions, par exemple :

import('chemin-module')
      .then(obj => console.log(obj))
      .catch(err => console.error(err))

10. Manipulation du Document HTML

10.1 Sélection d'éléments avec les méthodes modernes

Une API (application programming interface) est un ensemble d'identifiants qu'un programme offre à d'autres programmes pour interagir avec lui. Window est l'objet global dans un navigateur. Il représente la fenêtre de l'onglet utilisé par votre application.

Le DOM (Document Object Model) est une API permettant d'interagir avec des documents. Le DOM est un arbre de nœuds qui représente le document HTML. Pour accéder aux nœuds voisins du DOM

  • parentNode : Permet de retourner le nœud parent.
  • children : Permet de retourner les éléments enfants.
  • childNodes : Permet de retourner une collection itérable des nœuds enfants directs.
  • firstChild : Permet de retourner le premier enfant direct.
  • lastChild : Permet de retourner le dernier enfant direct.
  • previousSibling : Permet de retourner le nœud frère précédent.
  • nextSibling : Permet de retourner le nœud frère suivant.
  • nextElementSibling : Permet de retourner l'élément frère postérieur.

Pour sélectionner des éléments du DOM, on peut utiliser les méthodes getElements() mais celle-ci ne sont plus trop utilisé : 

  • document.getElementById()
  • document.getElementsByName()
  • element.getElementsByClassName()
  • element.getElementsByTagName()

Pour sélectionner des éléments du DOM, on utilise maintenant les méthodes querySelector, elles sont plus puissantes grâce aux sélecteurs CSS :

element.querySelector('.test[alt="test"]') // Retourne le premier élément sélectionné par le sélecteur.
element.querySelectorAll('#oui') // Retourne tous les éléments sélectionnés par le sélecteur passé en argument. Elle retourne une NodeList.

La méthode closest() s'utilise sur un élément et prend en argument un sélecteur CSS. Elle permet de sélectionner l'ancêtre le plus proche correspondant au sélecteur :

document.querySelector(".employe").closest(".liste-employes");

Les méthodes querySelector retournent des collections statiques et les méthodes getElementsBy des collections live. La différence est que les collections live se mettent à jour automatiquement lorsqu'il y a des changements dans le document, alors que les collections statiques non.

Pour modifier des éléments du DOM avec différentes propriétés comme innerHTML, outerHTML, textContent, innerText, data, nodeValue...

10.2 Modification du contenu et des attributs

Pour accéder et modifier aux attributs HTML d'un élément, on peut utiliser les méthodes suivantes :

  • element.hasAttribute(nom) : Permet de savoir si l'élément à un attribut.
  • element.getAttribute(nom) : Permet d'obtenir l'attribut d'un élément.
  • element.setAttribute(nom, valeur) : Permet d'ajouter un attribut à un élément.
  • element.removeAttribute(nom) : Permet de supprimer un attribut d'un élément.

On doit utiliser les attributs data-* et la propriété dataset pour définir des attributs personnalisés dans le HTML, par exemple :

<div id="test" data-id="42"></div>
document.querySelector('#test').dataset.id; // 42

10.3 Interaction avec les formulaires

Tous les inputs HTML deviennent des éléments HTMLInputElement sur le DOM, voici les méthodes principales :

  • input.focus();
  • input.blur();
  • input.click();
  • input.select();
  • input.setCustomValidity();
  • input.checkValidity();
  • input.reportValidity();

Pour voir la liste complète, consultez la documentation.

10.4 Manipulation des classes

Pour modifier du style et des classes sur du HTML, on peut utiliser className

element.className = "content red"

La propriété classList permet d'accéder à de nombreuses méthodes très utiles pour manipuler les class, par exemple :

  • element.classList.add(nom) : Permet d'ajouter la class passée en argument sur l'élément.
  • element.classList.remove(nom) : Permet de supprimer la class passée en argument sur l'élément.
  • element.classList.toggle(nom) : Permet d'ajouter la class passée en argument si elle est absente, ou de la supprimer si elle est présente.
  • element.classList.contains(nom) : Permet de savoir si la class existe sur l'élément. La méthode retourne un booléen.

Pour obtenir les styles calculés, il est nécessaire d'utiliser la fonction globale getComputedStyle(element) :

getComputedStyle(document.querySelector("p")).color // rgb(0, 0, 255)

10.5 Création et manipulation des éléments

Pour créer un élément il suffit d'utiliser la méthode document.createElement(balise) et de lui passer le nom de la balise de l'élément à créer :

document.createElement('p');

Pour créer un noeud texte il faut utiliser la méthode document.createTextNode(chaine) et lui passer la chaîne de caractères en argument :

document.createTextNode('Bienvenue à Gattaca');

Pour créer un noeud d'attribut il faut utiliser la méthode document.createAttribute(nom).

Pour insérer des noeuds contenant du HTML, il y a quelques méthodes comme insertAdjacentHTML(position, html), beforebegin(), afterbegin(), beforeend(), afterend(), appendChild(), insertBefore()... par exemple : 

element.before('<p>Test</p>');

Pour supprimer et remplacer des noeuds on peut utiliser la méthode noeud.remove() qui permet de supprimer le noeud ciblé et la méthode noeud.replaceWith() qui permet de remplacer le noeud avec un ou plusieurs noeuds ou avec une chaîne de caractères.

11. Événements

11.1 Gestion des événements

Un événement est un signal que quelque chose est arrivé, tous les nœuds du DOM émettent des événements. Les événements sont émis mais encore faut-il les écouter pour y réagir, pour cela il faut ajouter un écouteur d'événement sur l'élément qui émet l'événement ciblé. Voici quelques types d'évènements :

  • Glisser-déposer (DragEvent)
  • Événements de la souris (MouseEvent)
  • Événements du clavier (KeyboardEvent)
  • Événements de requêtes HTTP (FetchEvent)
  • Événements liés au focus (FocusEvent)

Il est possible de déclarer un gestionnaire d'événement sur un événement particulier en utilisant une propriété du DOM sur tous les nœuds du DOM (onclick, onmouseenter...), cette méthode n'est pas recommandée, par exemple :

noeud.onmouseover = () => {
    // code ici
}

La méthode addEventListener() permet d'ajouter un gestionnaire d'événement à un nœud du DOM, c'est la méthode qui est recommandé pour écouter des évènements, par exemple :

noeud.addEventListener(evenement, gestionnaire, options);
noeud.addEventListener("mouseover", () => {
    // code ici
});

La méthode handleEvent() reçoit en argument un objet Event, par exemple :

const test = { handleEvent(event) {// code ici} };

11.2 Suppression d'écouteurs

La méthode removeEventListener() permet de supprimer un gestionnaire d'événement sur un nœud, par exemple : 

noeud.removeEventListener(evenement, gestionnaire, options);

11.3 Création et émission d'événements personnalisés

On peut créer des événements personnalisés ou utiliser des événements natifs et les émettre quand on le souhaite :

const evenement = new Event("mouseover");
noeud.dispatchEvent(evenement);
const evenement = new MouseEvent("mouseover", {
    // code ici
});

12. Phases de propagation des événements

12.1 Bouillonnement et capture d'événements.

Le bouillonnement (bubbling) cela signifie que le navigateur va prendre l'élément le plus imbriqué (dernier) et exécuter le gestionnaire d'événement, puis il va exécuter le gestionnaire d'événement de son parent, et du parent de son parent, ainsi de suite jusqu'à l'élément racine <html>.

La phase de capture est la propagation de l'événement depuis l'élément le plus haut dans l'arbre jusqu'à l'élément le plus bas dans l'arbre. La phase de capture est très rarement utilisée, tous les écouteurs ne sont pas par défauts actifs sur la phase de bouillonnement. Pour activer cela, il faut mettre en option :

{ capture: true }

12.2 Délégation d'événements

Le pattern de la délégation d'événement consiste à mettre un écouteur d'événement sur un élément ancêtre commun des éléments cibles et de mettre toute la logique dans un seul gestionnaire d'événement.

La méthode element.closest() renvoie l'élément ancêtre le plus proche, correspondant aux sélecteurs, de l'élément sur lequel est utilisé la méthode.

12.3 Arrêt de la propagation

Nous pouvons stopper la propagation de l'événement que ce soit en phase de bouillonnement ou de capture avec la méthode stopPropagation(), par exemple :

element.stopPropagation();

Pour empêcher le comportement par défaut du navigateur, le clic sur un lien permet d'initier une navigation sur l'URL ciblée ou autre (soumission de formulaire). Mais parfois, on veut les remplacer par des comportements personnalisés, par exemple :

element.addEventListener("click", event => {
    event.preventDefault();
});

13. Promesses et Asynchronisme

13.1 Utilisation de Timeout et Interval

JavaScript a une exécution synchrone, mais si une requête HTTP est envoyée, JavaScript ne reste pas bloqué en attendant la réponse. C'est pour cela qu'on dit que JavaScript est non bloquant. Le moteur n'attend pas la fin d'une tâche asynchrone pour passer aux autres instructions.

La fonction globale setTimeout() est asynchrone, elle permet de définir un minuteur qui va déclencher l'exécution d'une fonction de rappel lorsque le temps est écoulé, par exemple :

setTimeout(() => console.log('Salut !'), 1000);

La fonction globale clearTimeout() permet d'annuler le minuteur, et donc l'exécution de la fonction de rappel, comme dans l’exemple suivant :

clearTimeout(identifiant);

La fonction globale setInterval() est asynchrone, elle permet d'exécuter une fonction de rappel de façon répétée selon l'intervalle spécifiée, par exemple :

setInterval(() => console.log('Salut !'), 1000);

La fonction globale clearInterval() permet d'annuler la fonction setInterval(), et donc l'exécution de la fonction de rappel, comme dans l’exemple suivant :

clearInterval(identifiant);

13.2 Promesses et gestion des états

Les promesses représentent une valeur qui sera disponible dans le futur, elles promettent qu'elles retourneront une valeur dans le futur. Une promesse peut être dans un des trois états suivants : état initial pending (en attente), peut être tenue (fulfilled) ou rompue (rejected). Quand elle a atteint un de ces deux états on dit qu'elle est acquittée (settled).

La fonction passée à une promesse n'est pas une fonction de rappel. Elle est exécutée immédiatement avant que l'objet Promise soit retourné. Une promesse va donc exécuter du code asynchrone dans sa fonction exécutrice, ensuite il faut obligatoirement appeler l'une des deux fonctions de rappel passées en argument en fonction du résultat des tâches asynchrones. Une promesse est un objet natif JavaScript Promise spécifique qui se déclare comme ceci :

new Promise((resolve, reject) => ());

13.3 Chaining des promesses

La méthode then() s'utilise sur une promesse et prend un ou deux arguments. Elle permet de gérer la valeur de retour en cas de rejet ou de succès, par exemple : 

const unePromesse = new Promise((resolve, reject) => {
    setTimeout(() => resolve(42), 1000);
});
unePromesse.then(
    resultat => console.log(resultat),
    erreur =>  console.log(erreur)
);

La méthode catch() est un raccourci de la méthode then() lorsque nous ne sommes intéressé que par le retour du rejected() (il est recommandé de mettre un catch() à la fin pour que toutes les erreurs de la chaîne y soit transmises), par exemple :

unePromesse.catch(erreur => /* code */ );

La méthode finally() prend une seule fonction de rappel en argument qui est appelée que la promesse soit tenue ou rompue, comme dans le code suivant :

unePromesse.finally(fonction);

Pour chaîner des promesses, par exemple avec then() on peut : 

new Promise((resolve, reject) => setTimeout(() => resolve(22), 3000))
    .then(r => r * 2)
    .then(r => r + 4)
    .catch(err => console.error(err.message))

Il y a différentes méthodes sur l'objet Promise, comme :

Promise.resolve(); // Créer directement un objet Promise acquitté et tenue
Promise.reject(); // Créer directement un objet Promise acquitté et rompue
Promise.all([
    new Promise(resolve => setTimeout(() => resolve(1), 3000)),
    new Promise(resolve => setTimeout(() => resolve(2), 2000)),
]).then(alert); // Permet d'effectuer des traitements asynchrones en parallèle et d'attendre que tous ces traitements soient terminés pour retourner une valeur
Promise.allSettled() // Renvoie une promesse qui est résolue une fois que l'ensemble des promesses passée en argument sont réussies ou rejetées
Promise.race() // Renvoie une promesse qui est résolue ou rejetée dès qu'une des promesses passée en argument est résolue ou rejetée

Un polyfill est du code d'une version plus ancienne de JavaScript qui permet de simuler une fonctionnalité récente du langage et qui n'est pas encore disponible dans toutes les versions des navigateurs.

13.4 Asynchrone

Le mot clé async devant une fonction permet de créer une fonction asynchrone qui s'exécute de façon asynchrone en utilisant une promesse, par exemple :

async function asynshrone() {
    return 42;
    // Ou
    return Promise.resolve(42);
}

L'opérateur await permet d'attendre la résolution d'une promesse uniquement dans une fonction asynchrone (elle permet d'attendre l'acquittement d'une promesse), par exemple :

async function asynshrone() {
    const valeur = await promesse;
}

Des traitements asynchrones peuvent être séquentiels : c'est-à-dire qu'ils sont effectués les uns après les autres.

Des traitements asynchrones peuvent être concurrents : c'est-à-dire qu'ils sont effectués tous en concurrence.

Des traitements asynchrones peuvent être parallèles : c'est-à-dire qu'ils sont effectués tous en même temps.

La boucle d'événements (event loop) est une boucle infinie qui permet de gérer tous les événements. Les Web APIs asynchrones utilisent des fils d'exécution (thread) distinctes du thread principal du moteur JavaScript qui exécute la pile d'exécution (la stack). Lorsque la tâche asynchrone est terminée, le gestionnaire de la tâche (par exemple le gestionnaire d'événement passé à addEventListener()) va être placé sur la file des tâches. La file des tâches fonctionne selon le principe FIFO, et donc la première tâche qui a été ajoutée est la première a être exécutée lorsque la pile est vide.

14. Gestion des requêtes HTTP

14.1 Requêtes GET

Les requêtes AJAX sont des requêtes effectuées en JavaScript qui n'entraînent pas un rechargement de la page. Elles permettent principalement une mise à jour dynamique de certaines parties des pages en JavaScript. Elles utilisent des Web API du navigateur : Fetch ou XMLHttpRequest que nous allons maintenant étudier.

Pour effectuer une requête GET en HTTP, il faut utiliser fetch comme dans l’exemple suivant :

const reponse = await fetch("https://jsonplaceholder.typicode.com/users");
response.then(async response => {
    try {
        const donnees = await reponse.json(); // Données au format JSON
    } catch (err) {
        console.error(err);
    }
}).catch(err => console.log(err));

14.2 Requêtes POST

La méthode POST d'une requête HTTP permet d'envoyer des données à un serveur. Pour effectuer une requête POST avec fetch :

const utilisateur = {};
const reponse = await fetch("https://jsonplaceholder.typicode.com/users", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify(utilisateur)
});

La liste complète des options qu'il est possible de passer à fetch est la suivante :

  • method : Méthode de la requête (GET/POST/HEAD/PATCH/PUT/DELETE).
  • headers : Les entêtes à ajouter à la requête.
  • body : Le corps de la requête envoyé avec les méthodes autres que GET/HEAD.
  • mode : cors, no-cors, ou same-origin.
  • credentials : omit, same-origin, ou include.
  • cache : default, no-store, reload, no-cache, force-cache ou only-if-cached.
  • redirect : follow, error, ou manual.
  • referrer : client, no-referrer, ou une URL.
  • referrerPolicy : no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin ou unsafe-url.
  • integrity : Un hash.
  • keepalive : Un booléen.
  • signal : Un AbortSignal.

14.3 Annulation de requêtes

Un AbortController permet d'annuler une requête en cours, il n'a qu'une propriété signal et une méthode abort(), par exemple :

const controleur = new AbortController();
fetch(url, {
    signal: controleur.signal
});
controleur.abort(); // annulation de la requête en cours

14.4 Gestion des codes de statut et erreurs

Les codes de statuts permettent d'indiquer si une requête HTTP a été exécutée avec succès ou non. Un message court l'accompagne :

  • 2xx signifie que la requête a fonctionné.
  • 3xx sont utilisés pour les redirections.
  • 4xx sont utilisés pour les erreurs côté client.
  • 5xx sont utilisés pour les erreurs côté serveur.

14.5 Contrôle du CORS

Le CORS (Cross-origin resource sharing) est un mécanisme de sécurité des navigateurs. Il permet de définir si un utilisateur peut accéder à des ressources d'un serveur situé sur une autre origine que le site courant. Par défaut, du JavaScript ne peut donc effectuer des requêtes que sur la même origine depuis laquelle il a été chargé (c'est ce qu'on appelle la Same-origin policy). Seul le serveur qui contrôle la ressource peut décider de sa politique CORS, il s'agit donc d'une problématique uniquement serveur. L'origine est définie par le navigateur comme entête de la requête HTTP. Elle ne peut pas être modifiée manuellement.

Les credentials (l'authentification) les requêtes CORS n'envoient donc pas les cookies relatifs au domaine d'origine et pas non plus les cookies relatifs au domaine cible, elles n'envoient pas non plus d'entête HTTP-Authorization (contenant par exemple un token JWT).

15. Manipulation des données de formulaire

15.1 Utilisation de l'objet FormData

FormData est un objet natif, il s'agit d'une Web API des navigateurs, qui permet d'envoyer un ensemble de paires clé / valeur et notamment des fichiers (récupérer les données d'un formulaire). Il existe plusieurs méthodes permettant de manipuler des FormData :

  • formData.append(nom, valeur) : permet d'ajouter un champ à l'objet avec le nom et la valeur indiqués.
  • formData.set(nom, valeur) : permet d'ajouter un champ à l'objet avec le nom et la valeur indiqués et de supprimer tous les autres champs avec le même nom. Cela permet de s'assurer qu'il n'y a aucun autre champ avec le même nom sur l'objet.
  • formData.delete(nom) : permet de supprimer le champ sur l'objet ayant le nom indiqué.
  • formData.get(nom) : permet de récupérer le champ sur l'objet ayant le nom indiqué.
  • formData.has() : permet de vérifier que le champ sur l'objet ayant le nom indiqué est présent, la méthode retourne un booléen.
  • formData.append(nom, blob, nomDeFichier) : permet d'ajouter un champ fichier à l'objet avec des données passées au format blob (nous l'étudierons) et de spécifier le nom du fichier.
  • formData.set(nom, blob, nomDeFichier) : permet de définir la valeur du champ fichier ayant le nom indiqué sur l'objet avec des données passées au format blob et de spécifier le nom du fichier.

15.2 Utilisation de l'objet URL

L'objet URL est un objet natif qui permet de gérer facilement des URL en ayant des propriétés et des méthodes adaptées, elle a également de multiples méthodes, par exemple :

const url = new URL(url)

15.3 Utilisation de l'API XMLHttpRequest 

La Web API XMLHttpRequest permet également d'effectuer des requêtes AJAX comme fetch. Dans la majorité des cas nous vous conseillons d'utiliser l'API fetch. Une exception est le besoin d'obtenir l'état d'avancement d'un upload qui n'est pas encore possible avec fetch. Il y a de multiples méthodes, par exemple :

const requete = new XMLHttpRequest();

16. Travail avec les dates

16.1 Utilisation de l'objet Date

Date est un objet global JavaScript essentiel car il permet de manipuler les dates, par exemple :

Date(); // "Fri Jan 9 2020 16:17:20 GMT+0100 (heure normale d’Europe centrale)"
const date1 = new Date();
const date2 = new Date(1000 * 3600); // Thu Jan 01 1970 02:00:00 GMT+0100
const date3 = new Date(2020, 0, 8, 22, 40, 20); // Wed Jan 08 2020 22:40:20 GMT+0100

16.2 Formatage des dates

La méthode toISOString() renvoie la date sous forme d'une chaîne de caractères au format ISO, comme dans l’exemple suivant :

date.toISOString(); // YYYY-MM-DDTHH:mm:ss.sssZ = 2020-01-10T17:55:32.094Z

16.3 Obtention de timestamps

On peut obtenir un timestamp avec la méthode Date.now(), comme dans le code suivant :

const timestamp = Date.now(); // 1578680577197

La méthode Date.parse() permet d'obtenir un timestamp à partir d'une représentation de date passée en argument :

Date.parse("Jan 2, 2020"); // 1577919600000

Il est possible d'obtenir facilement le temps qui s'est écoulé entre deux dates, en faisant une soustraction de 2 timestamps. La liste des méthodes qui utilisent le fuseau horaire local du système pour renvoyer leur valeur :

  • setDate()/getDate() : Permet d'obtenir le jour du mois (1 à 31).
  • getDay() : Permet d'obtenir le jour de la semaine (0 à 6), 0 est dimanche.
  • setFullYear()/getFullYear() : Permet d'obtenir les quatre chiffres de l'année.
  • setMonth()/getMonth() : Permet d'obtenir l'index du mois (entre 0 et 11).
  • setHours()/getHours() : Permet d'obtenir l'heure (entre 0 et 23).
  • setMinutes()/getMinutes() : Permet d'obtenir les minutes (entre 0 et 59).
  • setSeconds()/getSeconds() : Permet d'obtenir les secondes (entre 0 et 59).
  • setMilliseconds()/getMilliseconds() : Permet d'obtenir les millisecondes (entre 0 et 999).
  • setTime() : Permet de définir une date en utilisant un timestamp.

Méthodes utiles pour l'affichage en date et heure locales :

  • toLocaleString() : Permet de renvoyer une chaîne de caractères correspondant à une date et une heure locale. Elle est extrêmement paramétrable.
  • toLocaleDateString() : Est un raccourci permettant d'afficher la partie de la date dans la locale.
  • toLocaleTimeString() : Est un raccourci permettant d'afficher la partie heure de la date dans la locale.

17. Interaction avec l'URL et historique de navigation

17.1 Utilisation de l'objet location

L'objet location est un objet global qui fait partie des Web API du navigateur. Il contient des informations relatives à l'URL du document de la page et permet l'utilisation de méthodes afin d'interagir avec cette URL, par exemple :

  • hash : Chaîne de caractères contenant un # suivi de l'identifiant du fragment de l'URL (exemple : #test dans https://restapi.fr/#test).
  • hostname : Chaîne de caractères contenant le nom de l'hôte ou domaine (exemple : restapi.fr pour https://restapi.fr:3000/test).
  • host : Chaîne de caractères contenant l'hôte c'est à dire l'hostname et le port (exemple : restapi.fr:3000 pour https://restapi.fr:3000/test).
  • href : Chaîne de caractères contenant l'URL entière (exemple : https://restapi.fr:300/#test pour https://restapi.fr:300/#test).
  • origin : Chaîne de caractères contenant l'origine, c'est-à-dire le protocole, le domaine et le port (exemple : https://restapi.fr:3000 pour https://restapi.fr:3000/test).
  • pathname : Chaîne de caractères contenant le chemin (exemple : /api/test pour https://restapi.fr:3000/api/test).
  • port : Chaîne de caractères contenant le port (exemple : 3000 pour https://restapi.fr:3000/api/test). Si l'URL n'a pas de port car elle utilise les ports par défaut (80 pour HTTP ou 443 pour HTTPS) alors cette propriété sera vide.
  • protocol : Chaîne de caractères contenant le protocole suivi d'un : (exemple : https: pour https://restapi.fr:3000/api/test).
  • search : Chaîne de caractères contenant les paramètres (exemple : ?user=paul&gender=m pour https://restapi.fr:3000/api/test?user=paul&gender=m).
  • assign() : Permet de charger l'URL passé en argument.
  • reload() : Permet de recharger la page de l'URL actuelle. Elle prend un argument optionnel, qui est un booléen. Si vous lui passez true cela force le navigateur à recharger la page en envoyant un GET au serveur sans avoir recours au cache.
  • replace() : Permet de remplacer la page en passant une URL en argument. La différence avec la méthode assign() est que l'historique de la page courante est supprimé. Cela signifie que l'utilisateur ne pourra pas utiliser le bouton précédent de son navigateur pour retourner sur la page actuelle.
  • toString() : Permet de simplement renvoyer l'URL sous une chaîne de caractères, la même que celle contenue dans la propriété href.

17.2 Manipulation de l'historique de navigation

L'objet global history permet d'agir sur l'historique de navigation du navigateur, avec :

  • history.back() : Permet de retourner sur la page précédente de l'historique. Elle équivaut à si l'utilisateur cliquait sur le bouton précédent de son navigateur.
  • history.forward() : Permet d'aller sur la page suivante de l'historique de navigation. Elle équivaut à si l'utilisateur cliquait sur le bouton suivant de son navigateur.
  • history.go() : Permet d'aller sur une page dans l'historique de navigation en passant en argument un nombre qui identifie la page relativement à la page courante. Par exemple go(-1) équivaut à back() et go(1) à forward(). Mais il est possible de faire go(-4) pour remonter quatre pages en arrière.
  • history.pushState() : Permet d'ajouter une entrée dans l'historique de navigation.
  • history.replaceState() : Permet de modifier une entrée de l'historique de navigation.

18. Objets, classes et héritage

18.1 Constructeurs d'objets

L'intérêt principal d'un constructeur est de ne pas se répéter :

function Utilisateur(nom, prenom) {
    this.nom = nom;
    this.prenom = prenom;
    this.dateCreation = new Date();
}
const jean = new Utilisateur("Jean", "Dupont");
const paul = new Utilisateur("Paul", "Roche");

18.2 Définir des objets et méthodes

L'héritage est le mécanisme qui permet à des objets d'utiliser des propriétés et des méthodes de modèles sans avoir à les réécrire. En JavaScript, tous les objets ont une propriété spéciale interne le [[Prototype]] ou __proto__. Lorsque nous tentons d'utiliser une propriété ou une méthode sur un objet, le moteur va d'abord regarder sur l'objet. S’il ne la trouve pas sur l'objet, il va regarder si elle existe sur le [[Prototype]] de l'objet.

Le pattern constructeur en JavaScript est celui utilisant une fonction constructrice et l'opérateur new pour instancier de nouveaux objets. Object est une fonction constructrice qui permet de créer les objets JavaScript :

typeof Object; // function
const test = new Object(); // ou le raccourci {}
test.constructor === Object; // true
test.__proto__ === Object.prototype; // true

Plutôt que d'utiliser __proto__, il faut utiliser la méthode getPrototypeOf() :

Object.getPrototypeOf(objet);

Object.create() permet de créer un nouvel objet en passant en argument le [[ Prototype ]] de l'objet créé :

const animal = {
    vivant: true
};
const tigre = Object.create(animal);
tigre.pattes = 4;
Object.getPrototypeOf(tigre) === animal; // true

18.3 Utilisation d'accesseurs (getter) et mutateurs (setter)

Les accesseurs permettent de renvoyer des valeurs dynamiquement calculées en utilisant les autres propriétés d'un objet. Un accesseur se définit avec le mot clé get :

const obj = {
    get unGetter() {
        return "ok";
    },
};

Un mutateur permet de lier une propriété d'un objet à une fonction qui sera exécutée pour calculer la valeur finale de la propriété dynamiquement à chaque affectation. Un mutateur se définit avec le mot clé set :

const obj = {
    set unSetter() {
        return "ok";
    },
};

18.4 Class et POO

Les classes simplifient pour les développeurs provenant de langages utilisant l'héritage multiple. Les classes en JavaScript sont très proches des fonctions constructrices. Elles permettent d'écrire par exemple :

class UneClasse {
    constructor(prenom) {
        this.prenom = prenom;
    }
    direBonjour() {
        console.log('Bonjour !');
    }
    get prop() {
        return this._prop;
    }
    set prop(valeur) {
        if (valeur.length < 3) {
            console.log("Trop court.");
            return;
        }
        this._prop = valeur;
    }
}
const paul = new UneClasse('Paul');

Il existe un mot clé spécial permettant d'appeler des méthodes et le constructeur de la classe parente depuis la classe fille : super. A noter que si vous utilisez un constructor dans une classe fille il faut obligatoirement appeler super() dans son constructor et en premier, avant de définir les propriétés.

Il est possible de faire hériter une classe d'une autre classe. Dans ce cas on appelle la classe qui a hérité la classe fille et l'autre classe, la classe parente. Pour faire hériter une classe d'une autre classe on utilise extends :

class UneClasseFille extends UneClasseParente {}

Le mot-clé static permet de définir des méthodes statiques sur une classe. Cela signifie que la méthode ne sera pas disponible sur les instances de cette classe, par exemple :

class Personne {
    constructor(nom) {
        this.nom = nom;
    }
    static decrire () {
        console.log('Personne est une classe pour créer des personnes.');
    }
}
const bob = new Personne('Bob');
Personne.decrire(); // Personne est une classe pour créer des personnes.
bob.decrire(); // Error : dylan.decrire is not a function

Dans les class, les propriétés et méthodes internes qui sont appelées privées, par convention elles sont préfixées avec un _ :

class UneClasse {
    get propPublique() {
        return this._propPrivee;
    }
    set propPublique(valeur) {
        if (condition) {
            this._propPrivee = valeur;
        }
    }
}

L'opérateur instanceof permet de savoir si un objet est une instance d'un constructeur donné. Cela permet de savoir plus exactement si le prototype d'une fonction constructeur se trouve sur la chaîne des prototypes d'un objet, par exemple :

class UneClasse {};
const test = new UneClasse();
console.log(test instanceof UneClasse); // true

En programmation orientée objet, un mixin permet de partager des fonctionnalités avec plusieurs classes. Cela signifie que plusieurs classes et les instances de ces classes peuvent utiliser ces fonctionnalités sans avoir à hériter des mixins. En JavaScript, ce pattern s'utilise comme suit :

class A {}
class B {}
const mixin = {
    methode1() {},
    methode2() {}
};
Object.assign(A.prototype, mixin);
Object.assign(B.prototype, mixin);
const instanceA = new A();
const instanceB = new B();
instanceA.methode1();
instanceB.methode2();

19. Manipulation des Données en JavaScript

19.1 Gestion des exceptions

L'opérateur throw permet de lever une exception, par exemple :

const data = '{ "prenom": "Jean" }';
try {
    const utilisateur = JSON.parse(data);
    if (!utilisateur.age) {
        throw new SyntaxError("Il manque le paramètre âge !");
    }
} catch(e) {
    console.log(e.message); // Il manque le paramètre âge !
}

19.2 Utilisation des cookies

Pour accéder aux cookies dans le navigateur il suffit de faire :

console.log(document.cookie);

Pour créer des nouveaux cookies côté navigateur :

document.cookie = "clé=valeur; Max-Age=5";
document.cookie = "clé=" + JSON.stringify(objet) + ';expires=' + date;

Pour mettre à jour un cookie, il suffit de réaffecter à une même clé une autre valeur :

document.cookie = "lang=FR";
document.cookie = "lang=EN"; // lang=EN

Consultez la liste des options des cookies JavaScript, via la documentation.

Un cookie first-party est un cookie créé pour le domaine (côté client ou serveur) qui est celui du site visité qui demande au navigateur de l'enregistrer. Les faux cookies first-party sont les cookies déposés par du JavaScript client provenant d'une autre origine. C'est-à-dire d'un domaine, d'un protocole ou d'un port différent.

Les cookies peuvent introduire des failles de sécurité telles que les attaques CSRF (Cross-Site Request Forgery). L'option samesite permet de contrôler non pas qui peut définir un cookie mais qui peut y accéder.

19.3 Utilisation de l'API localStorage et sessionStorage

L'API localStorage permet de sauvegarder des paires clé / valeurs dans le navigateur comme pour les cookies. Par exemple :

  • localStorage.setItem('clé', valeur); : Pour stocker une paire de clé.
  • localStorage.getItem('clé'); : Pour récupérer une paire de clé.
  • localStorage.removeItem('clé'); : Pour supprimer une paire de clé.
  • localStorage.clear(); : Pour supprimer totalement tout le localStorage.
  • localStorage.length; : Pour connaitre le nombre de paires clé/valeur sauvegardées.

L’API sessionStorage est exactement comme localStorage mais les données sont supprimées lors de la fermeture de l'onglet et qu'elles ne sont pas partagées entre les onglets, même s'ils ont la même origine.

19.4 Gestion des événements Storage

Les événements Storage sont déclenchés lorsqu'une valeur est mise à jour dans le localStorage ou le sessionStorage. Par exemple :

window.addEventListener('storage', () => { // Faire quelque chose });

20. Graphiques et Interactivité avec Canvas

20.1 Introduction aux éléments <canvas> en HTML

Un canvas est un élément HTML qui permet de modifier une zone via des script. En javascript on peut par exemple :

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

20.2 Dessin de formes simples et complexes

Pour dessiner une suite de lignes sur un canvas, on peut utiliser en javascript :

  • beginPath() : Définir une nouvelle suite de lignes.
  • moveTo(x, y) : Définir le point de départ du stylo.
  • lineTo(x, y) : Déplacer le stylo en traçant des lignes.
  • stroke() : Dessiner les lignes du parcours.
  • rect(x, y, largeur, hauteur) : Dessiner un rectangle.
  • closePath() : Finir une suite de ligne en retournant au départ.
  • fill() : Remplir des formes.

Les propriétés de traçage sur les canvas sont :

  • lineWidth : Pour définir la largeur des lignes tracées.
  • lineCap : Pour définir les extrémités des lignes tracées qui peuvent être butt, round ou square.
  • lineJoin : Pour définir les jonctions des lignes tracées qui peuvent être bevel, round ou miter.
  • strokeStyle : Pour définir la couleur d'un tracé qui peuvent être mot-clé, rbg, rbga, hsl ou hsla.

Dessiner des arcs de cercle et des cercles avec arc() et arcTo(), par exemple :

arc(x, y, rayon, angleInitial, angleFinal, sens);
arcTo(px1, py1, px2, py2, radius);

La méthode quadraticCurveTo() permet de dessiner une courbe quadratique de Bézier entre trois point : le point de départ, le point d'arrivé et le point de contrôle passé, par exemple :

quadraticCurveTo(pcx, pcy, p2x, p2y);

La différence avec une courbe quadratique est qu'il y a un point de contrôle supplémentaire vous permettant de dessiner des courbes plus complexes. Par exemple :

bezierCurveTo(pcx1, pcy1, pcx2, pcy2, p2x, p2y);

20.3 Transformation des formes

Lorsque vous faites des transformations il est conseillé de sauvegarder les propriétés de la grille avant afin de pouvoir la restaurer une fois un ou plusieurs dessins effectués. Pour se faire il faut utiliser les méthodes save() et restore(). On peut transformer les formes avec :

ctx.translate(x, y);
ctx.rotate(deg);
ctx.scale(x, y);

20.4 Chargement et manipulation d'images

Pour charger une image dans un canvas, on peut utiliser le code suivant :

const image = new Image();
image.src = lien;
image.onload = () => {
    ctx.drawImage(image, px, py, largeur, hauteur);
    // Ou
    createPattern(image, type);
};

Pour écrire du texte dans un canvas, on peut utiliser le code suivant :

ctx.font = "italic small-caps bold 18px/2 cursive";
fillText(text, x, y); // Pour écrire du texte avec les lettres remplies
strokeText(text, x, y); // Pour écrire le contour des lettres

20.5 Utilisation de dégradés et de motifs pour le remplissage

Pour créer des dégradés, il faut utiliser :

  • createLinearGradient(px1, py2, px2, py2); : Un dégradé de couleur vers une autre couleur.
  • gradient.addColorStop(0, 'blue'); : Pour définir la couleur de départ.
  • gradient.addColorStop(1, 'red'); : Pour définir la couleur de fin.
  • createRadialGradient(px1, py1, r1, px2, py2, r2); : Un dégradé de couleur radial, c'est-à-dire circulaire.

21. Gestion des Fichiers et Drag-and-Drop

21.1 Utilisation des objets Blob et File

Un Blob ou Binary Large Object est un objet contenant des données en binaire immuable. On utilise le code suivant : 

new Blob(source, options);

En JavaScript nous utilisons l'interface File, qui hérite de l'interface Blob, pour gérer les fichiers. Par exemple :

new File(source, nom, options);
const fichier = new File(["Coucou !"], "bienvenue.txt", {
    type: "text/plain",
    lastModified: Date.now()
});

21.2 Utilisation de FileReader

Un FileReader permet de lire le contenu de File ou de Blob de façon asynchrone. Il possède 4 méthodes :

  • readAsArrayBuffer() : Permet de lire le File ou le Blob et la propriété result contiendra un ArrayBuffer lorsque la lecture sera terminée.
  • abort() : Permet d'annuler la lecture du fichier, l'état readyState passera à 2 (pour DONE).
  • readAsText() : Permet de retourner une chaîne de caractères dans la propriété result lorsque la lecture sera terminée.
  • readAsDataURL() : Permet de retourner une chaîne encodée en Base64 dans la propriété result lorsque la lecture sera terminée. C'est utile pour utiliser le fichier dans la propriété src d'une image par exemple.

21.3 Mise en place du glisser-déposer

Le fonctionnement du Drag and Drop (glisser / déposer) est standardisé sur tous les navigateurs grâce à l'API HTML5. Il existe un certain nombre de types pour les événements DragEvent qui sont les suivants : drag, dragstart, dragend, dragenter, dragexit, dragleave, dragover et drop.

Pour identifier un élément du DOM qui peut être déplacé en drag and drop, il faut lui ajouter côté HTML l'attribut draggable et lui assigner la valeur true. Par exemple :

<p draggable="true">Cet élément est déplaçable</p>

21.4 Événements glisser-déposer

L'événement dragstart est émis une seule fois lors de chaque opération de glisser / déposer, lorsque l'utilisateur commence à glisser un élément avec l'attribut draggable à true. Par exemple :

document.addEventListener('dragstart', (event) => {
    console.log('dragstart : ', event);
});

L'événement drag est émis très fréquemment pendant le déplacement : une fois toutes les quelques centaines de millisecondes (la fréquence varie suivant les navigateurs).

L'événement dragend est émis une seule fois lors de chaque opération de glisser / déposer, lorsque l'utilisateur relâche la souris ou appuie sur la touche entrée.

L'événement dragenter est déclenché lorsqu'un élément en train d'être déplacé entre dans une zone de dépôt (drop zone).

L'événement dragleave est déclenché lorsqu'un élément, en train d'être déplacé, sort d'une zone de dépôt (drop zone).

L'événement dragover est déclenché lorsqu'un élément, en train d'être déplacé, est situé sur une zone de drop. L'événement est émis environ toutes les 100 millisecondes (la fréquence dépend des navigateurs) tant que l'élément est sur la zone de drop. Par exemple :

rightList.addEventListener('dragover', (event) => {
event.preventDefault();
console.log('dragover : ', event);
});

L'événement drop est déclenché lorsqu'un élément, en train d'être déplacé, est déposé dans une zone de dépôt (drop zone). Par exemple :

rightList.addEventListener('drop', (event) => {
    console.log('drop : ', event);
});

Définir des données utilisables lors du drag and drop avec DataTransfer. La propriété effectAllowed permet de spécifier les effets autorisés. La propriété dropEffect permet de définir l'effet à utiliser et à afficher à l'utilisateur lors des événements dragenter et dragover. Par exemple :

const rightList = document.querySelector(".list-right");
document.addEventListener("dragstart", event => {
    event.dataTransfer.setData("id", event.target.id);
    event.target.classList.add("drag");
    setTimeout(() => {
        event.target.classList.remove("drag");
    });
});
rightList.addEventListener("dragover", event => {
    event.preventDefault();
});
rightList.addEventListener("drop", event => {
    event.target.classList.remove("drop");
    const li = document.getElementById(event.dataTransfer.getData("id"));
    event.target.appendChild(li);
});