Synthèse des technologies front-end : partie JavaScript

Fonctions JavaScript : Déclarations et Expressions

En JavaScript, les fonctinos peuvent être déclarées de deux manières principales :

Déclaration directe de fonction

Une fonction déclarée directement peut être appelée n'importe où dans sa portée. L'identifiant est assigné à la fonction lors de la phase d'analyse du code.

function saluer() {
   console.log("Bonjour depuis la déclaration directe !");
}

Expression de fonction

Une fonction définie comme une expression ne peut être appelée qu'après sa déclaration. La variable est assignée à la fonction lors de l'exécution du code.

var afficherMessage = function() {
   console.log("Bonjour depuis l'expression de fonction !");
};
afficherMessage();

Fonctions ordinaires vs. Fonctions constructeurs

Les fonctions constructeurs sont une forme spéciale de fonctions ordinaires, conventionnellement nommées avec une majuscule. Leur but principal est la création d'instances d'objets. Elles se distinguent par leur mode d'invocation, nécessitant le mot-clé new. Le mot-clé this à l'intérieur d'un constructeur fait référence à la nouvelle instance créée. Le processus de création d'une instance suit ces étapes : création d'un nouvel objet, assignation de cet objet à this, exécution du code de la fonction, et retour de la nouvelle instance. L'opérateur instanceof permet de vérifier si un objet est une instance d'un constructeur particulier.

Héritage en JavaScript

Il existe plusieurs mécanismes pour implémenter l'héritage :

  • Héritage par emprunt de constructeur : Utilise call ou apply pour invoquer le constructeur parent dans le contexte du constructeur enfant.
  • Héritage prototypal : Le prototype de l'objet enfant pointe vers une instance de l'objet parenet.
  • Héritage combiné : Combine l'héritage prototypal pour les propriétés et méthodes partagées avec l'emprunt de constructeur pour les propriétés d'instance uniques.

L'héritage prototypal peut être affecté par la réécriture du prototype. L'héritage combiné est une approche courante qui permet la réutilisation du code tout en assurant que chaque instance possède ses propres propriétés.

Closures (Fermetures)

Une closure est une fonction déclarée à l'intérieur d'une autre fonction. Elle conserve l'accès aux variables et paramètres de sa fonction englobante, même après que celle-ci ait terminé son exécution. Les closures permettent d'accéder à des variables privées, de maintenir des états en mémoire, et d'éviter la pollution de l'espace global. Cependant, elles peuvent entraîner des fuites de mémoire si les ressources ne sont pas libérées correctement.

Prototype et Chaîne de prototypes

Chaque objet en JavaScript possède une propriété interne __proto__ qui pointe vers son prototype. Le prototype est lui-même un objet qui peut avoir son propre prototype, formant ainsi une chaîne de prototypes. Cette chaîne est utilisée pour l'héritage des propriétés et méthodes. Les méthodes comme hasOwnProperty() permettent de vérifier si une propriété appartient directement à l'objet, tandis que l'opérateur in vérifie la présence d'une propriété sur l'objet ou dans sa chaîne de prototypes.

const livre = {
   titre: "JavaScript Avancé",
   auteur: "Jean Dupont"
};

console.log(livre.hasOwnProperty('titre'));      // true
console.log(livre.hasOwnProperty('toString'));  // false

console.log("titre" in livre);                  // true
console.log("toString" in livre);               // true

Les fonctions possèdent également une propriété prototype, qui est l'objet utilisé comme prototype pour toutes les instances créées par cette fonction. Les propriétés et méthodes définies sur prototype sont partagées par toutes les instances.

function Utilisateur() {
   this.identifiant = 'Utilisateur123';
}

Utilisateur.prototype.afficherIdentifiant = function() {
   console.log(this.identifiant);
};

const utilisateur1 = new Utilisateur();
utilisateur1.afficherIdentifiant(); // Affiche 'Utilisateur123'

Sucre syntaxique et Littéraux

Le sucre syntaxique désigne des syntaxes qui améliorent la lisibilité et la facilité d'utilisation du code sans modifier les fonctionnalités fondamentales du langage. Les littéraux représentent des valeurs fixes dans le code, comme les littéraux numériques (entiers, flottants, hexadécimaux) et les littéraux de chaîne de caractères délimités par des guillemets simples ou doubles.

La fonction eval()

La fonction eval() interprète et exécute une chaîne de caractères comme du code JavaScript. Son utilisation est fortement déconseillée en raison de risques de sécurité et de mauvaises performances.

apply(), call() et bind()

Ces trois méthodes permettent de définir le contexte d'exécution (la valeur de this) pour une fonction. apply() et call() invoquent immédiatement la fonction, la première acceptant les arguments sous forme de tableau, la seconde sous forme d'arguments séparés par des virgules. bind() crée une nouvelle fonction avec le contexte défini, mais ne l'exécute pas immédiatement.

Propagation, Capture d'événement et Délégation d'événement

  • Propagation (Bubbling) : Les événements déclenchés sur un élément se propagent vers ses ancêtres.
  • Capture : L'événement est traité en commençant par les ancêtres jusqu'à l'élément cible. La méthode addEventListener permet de spécifier le mode de capture (troisième argument).
  • Délégation d'événement : Une seule fonction de gestion d'événement est attachée à un élément parent pour gérer les événements des éléments enfants, en tirant parti de la propagation.

Copie superficielle (Shallow Copy) et Copie profonde (Deep Copy)

Pour les types de données primitifs, l'assignation crée des copies indépendantes. Pour les types de données de référence (objets, tableaux), l'assignation simple copie la référence, entraînant des modifications partagées. La copie superficielle crée un nouvel objet mais copie les références aux objets imbriqués. Des méthodes comme Object.assign() et Array.prototype.slice() réalisent des copies superficielles. La copie profonde crée un nouvel objet et copie récursivement toutes les propriétés imbriquées. Des techniques comme JSON.parse(JSON.stringify(objet)) ou des bibliothèques spécialisées sont utilisées pour les copies profondes.

Écouteurs d'événements

  • addEventListener(type, listener) : Attache un écouteur d'événement.
  • removeEventListener(type, listener) : Supprime un écouteur d'événement.
  • stopPropagation() : Empêche la propagation de l'événement.
  • preventDefault() : Empêche le comportement par défaut de l'événement.
  • event.target ou event.srcElement : Récupère l'élément cible de l'événement.

CommonJS vs. ES Modules

  • CommonJS : Standard utilisé par Node.js, utilisant require() pour importer et module.exports pour exporter. Les exports sont des copies des valeurs. L'importation se fait dynamiquement.
  • ES Modules : Standardisé dans ECMAScript 6, utilisant import et export. Les exports créent des références dynamiques. L'importation est statique et doit se faire en haut du fichier.

Vérification des types en JavaScript

  • typeof : Renvoie une chaîne indiquant le type primitif ('string', 'number', 'boolean', 'undefined', 'symbol', 'object', 'function'). Attention, typeof null renvoie 'object'.
  • instanceof : Vérifie si un objet est une instance d'un constructeur donné en parcourant sa chaîne de prototypes.
  • Object.prototype.toString.call() : Méthode robuste pour identifier le type de n'importe quel objet, y compris les types primitifs et les objets natifs.
  • Array.isArray() : Vérifie spécifiquement si une valeur est un tableau.

Quand éviter les fonctions fléchées

Les fonctions fléchées (arrow functions) ont des particularités à considérer :

  • this lexical : Le this est lié au contexte de définition, pas d'appel.
  • Pas de constructeur : Ne peuvent pas être utilisées avec new.
  • Pas d'objet arguments : Utiliser les paramètres rest (...args) à la place.
  • Pas de yield : Ne peuvent pas être utilisées comme générateurs.

Il faut éviter les fonctions fléchées dans les méthodes d'objet, les méthodes de prototype, les constructeurs et les callbacks dont le this doit être dynamique.

Débouncing (Antirebond) et Throttling (Limitation de débit)

Ces techniques servent à contrôler la fréquence d'exécution des fonctions.

  • Débouncing : Exécute la fonction seulement après qu'un certain temps se soit écoulé sans nouvelle invocation. Utile pour les saisies utilisateur (autocomplétion) ou le redimensionnement de fenêtre.
  • Throttling : Garantit qu'une fonction n'est exécutée qu'une fois par intervlale de temps défini. Utile pour les événements de défilement (scroll) ou les mouvements de souris fréquents.
// Exemple de Debounce (exécution finale)
function debounce(func, wait) {
   let timer;
   return function(...args) {
       clearTimeout(timer);
       timer = setTimeout(() => {
           func.apply(this, args);
       }, wait);
   };
}

// Exemple de Throttle (version timestamp)
function throttle(func, wait) {
   let previous = 0;
   return function(...args) {
       const now = Date.now();
       if (now - previous > wait) {
           func.apply(this, args);
           previous = now;
       }
   };
}

Problèmes de précision avec les nombres décimaux

La conversion des nombres décimaux en binaire peut entraîner des pertes de précision. Pour y remédier, on peut utiliser Number.EPSILON, la méthode toFixed(), ou convertir les nombres en entiers avant le calcul en les multipliant par une puissance de 10.

Étiquettes: JavaScript fonctions Héritage closures prototype

Publié le 26 juin à 20h31