Les Fondamentaux de JavaScript Moderne

Syntaxe ES6+

Let, const et scoping

En ES6, let et const remplacent var pour améliorer la gestion des variables.

let x = 10;
const y = 20;

:warning: Attention à la différence entre réassignation et modification d’objet. const empêche la réassignation, mais les propriétés d’un objet déclaré avec const restent modifiables.

const obj = { name: 'Alice' };
obj.name = 'Bob'; // Pas d'erreur, seule la référence reste constante.

Privilégier const chaque fois que possible.

Fonctions fléchées

Les fonctions fléchées (arrow functions) simplifient la syntaxe et capturent le this lexicalement.

const add = (a, b) => a + b;

Différences par rapport aux fonctions classiques :


Template Literals

Les template literals introduits avec ES6 permettent d’incorporer des expressions dans des chaînes de caractères de manière plus concise que la concaténation classique.

let name = 'Alice';
let greeting = `Bonjour, ${name}!`; // Bonjour, Alice!

:bulb: Les sauts de ligne sont conservés dans les chaînes de caractères.

let message = `Ceci est
une chaîne multilignes.`;

Destructuration et Opérateur Spread/Rest

Destructuration

La destructuration permet d’extraire des valeurs d’objets ou de tableaux en une seule ligne.

const person = { name: 'Alice', age: 25 };
const { name, age } = person;
console.log(name); // Alice

Pour les tableaux :

const numbers = [1, 2, 3];
const [first, second] = numbers;

Spread (...)

L’opérateur spread permet de décomposer un tableau ou un objet.

const arr1 = [1, 2];
const arr2 = [...arr1, 3]; // [1, 2, 3]

Rest (...)

L’opérateur rest collecte des arguments restants.

function sum(...numbers) {
  return numbers.reduce((acc, n) => acc + n, 0);
}

spread et rest simplifient la manipulation des données complexes.


Types et Structures de Données

JavaScript est un langage dynamique, ce qui signifie qu’il n’y a pas de types statiques prédéfinis. Cela permet une grande flexibilité, mais nécessite également une gestion des types et des structures de données efficace.

Type dynamique vs TypeScript

JavaScript est un langage à typage dynamique, ce qui signifie que les types des variables sont déterminés au moment de l’exécution. En revanche, TypeScript introduit un système de types statiques où les types des variables, des fonctions et des objets sont définis au moment de la compilation.

Tableaux, objets, et Map/Set

Tableaux (Array) et Objets sont les deux structures de données fondamentales en JavaScript.

Les Map et Set sont des structures de données plus récentes et plus puissantes, introduites avec ES6, offrant des fonctionnalités améliorées par rapport aux objets classiques.

WeakMap et WeakSet

Les WeakMap et WeakSet sont similaires aux Map et Set, mais avec des différences importantes : elles ne conservent pas de références fortes aux objets qu’elles contiennent. Cela signifie que si un objet est collecté par le ramasse-miettes (garbage collector), il est automatiquement supprimé de la collection.

Manipulation des structures via les méthodes modernes (map, filter, reduce)

JavaScript moderne propose plusieurs méthodes de manipulation des tableaux qui permettent d’écrire du code plus fonctionnel et déclaratif.


Programmation Asynchrone

La programmation asynchrone est essentielle pour écrire des applications JavaScript réactives et efficaces. En raison de son modèle basé sur un seul fil d’exécution, JavaScript utilise des mécanismes asynchrones pour éviter de bloquer l’exécution du code tout en attendons des réponses longues (comme des requêtes réseau, des opérations sur des fichiers, etc.).

Promesses et async/await

Une promesse (Promise) est un objet représentant l’achèvement (ou l’échec) d’une opération asynchrone. Elle permet de chaîner des opérations asynchrones et de gérer les erreurs plus efficacement.

let myPromise = new Promise((resolve, reject) => {
  let success = true;
  if(success) {
    resolve('Opération réussie');
  } else {
    reject('Opération échouée');
  }
});

myPromise.then(result => {
  console.log(result); // "Opération réussie"
}).catch(error => {
  console.log(error); // "Opération échouée"
});

async/await introduits avec ES2017 (ES8) sont une syntaxe plus simple pour travailler avec des promesses.

async function fetchData() {
  try {
    let response = await fetch('https://api.example.com/data');
    let data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Erreur:', error);
  }
}

Différences entre callbacks et Promises

Les callbacks sont la méthode traditionnelle pour gérer des opérations asynchrones. Cependant, les callbacks peuvent entraîner des problèmes comme le callback hell ou pyramide de l’enfer, où les callbacks imbriqués deviennent difficiles à gérer.

// Callback hell
doSomething((err, result) => {
  if (err) {
    console.error(err);
  } else {
    doSomethingElse(result, (err, result2) => {
      if (err) {
        console.error(err);
      } else {
        doThirdThing(result2, (err, result3) => {
          if (err) {
            console.error(err);
          } else {
            console.log(result3);
          }
        });
      }
    });
  }
});

Les promesses et async/await offrent une meilleure gestion des erreurs et permettent d’éviter le code imbriqué, rendant l’asynchrone plus lisible et facile à maintenir.

API Fetch et gestion des erreurs

La fonction fetch est une API moderne pour effectuer des requêtes HTTP de manière asynchrone. Elle renvoie une promesse, ce qui permet de chaîner .then() pour traiter la réponse.

fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Il y a eu un problème avec la requête Fetch :', error));

Utiliser async/await avec fetch permet de rendre le code encore plus lisible.

async function getData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) throw new Error('Erreur réseau');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Erreur de récupération des données :', error);
  }
}

Chaining de Promesses

Une des grandes puissances des promesses est le chaînage. En utilisant .then(), on peut exécuter plusieurs actions de manière séquentielle.

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => {
    console.log('Données récupérées:', data);
    return fetch(`https://api.example.com/details/${data.id}`);
  })
  .then(response => response.json())
  .then(details => console.log('Détails récupérés:', details))
  .catch(error => console.error('Erreur:', error));

Parallelisme avec Promise.all

Parfois, on souhaite exécuter plusieurs tâches asynchrones en parallèle. Promise.all() permet de gérer plusieurs promesses à la fois et d’attendre que toutes soient résolues.

let promise1 = fetch('https://api.example.com/data1');
let promise2 = fetch('https://api.example.com/data2');

Promise.all([promise1, promise2])
  .then(responses => Promise.all(responses.map(response => response.json())))
  .then(data => {
    console.log('Données des deux requêtes:', data);
  })
  .catch(error => console.error('Erreur:', error));

Concepts Avancés

Une fois que les bases du langage sont maîtrisées, il est important de comprendre certains des concepts avancés qui rendent JavaScript puissant et flexible. Ces concepts permettent de gérer la portée, l’héritage, les comportements asynchrones et les modèles de conception.

Closures et portée lexicale

Une closure (fermeture) est une fonction qui se souvient de son environnement lexical, même lorsqu’elle est exécutée en dehors de ce contexte. Cela signifie qu’une fonction interne peut accéder aux variables de la fonction externe qui l’a créée, même après que celle-ci a terminé son exécution.

function outer() {
  let count = 0;
  return function inner() {
    count++;
    console.log(count);
  }
}

let increment = outer();
increment(); // 1
increment(); // 2

Cela permet de créer des data encapsulation ou des variables privées dans un contexte où JavaScript ne supporte pas directement la notion de visibilité des membres d’une classe.

Modules ES (import/export)

Les modules ES sont un moyen de structurer et organiser le code JavaScript en morceaux réutilisables. ES6 introduit les mots-clés import et export pour permettre l’importation et l’exportation de blocs de code, ce qui facilite la gestion de projets JavaScript plus complexes.

// math.js
export function add(a, b) {
  return a + b;
}
export const pi = 3.14;
// main.js
import { add, pi } from './math.js';
console.log(add(2, 3)); // 5
console.log(pi); // 3.14

Design Patterns simples en JS

Les design patterns (ou patrons de conception) sont des solutions éprouvées à des problèmes récurrents. En JavaScript, les patrons sont souvent utilisés pour structurer des applications ou gérer des comportements réutilisables.

class Singleton {
  constructor() {
    if (!Singleton.instance) {
      Singleton.instance = this;
    }
    return Singleton.instance;
  }
}

const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
class Animal {
  constructor(name) {
    this.name = name;
  }
}

class AnimalFactory {
  static createAnimal(type, name) {
    switch (type) {
      case 'dog':
        return new Animal(name);
      case 'cat':
        return new Animal(name);
      default:
        throw new Error('Unknown animal type');
    }
  }
}

let dog = AnimalFactory.createAnimal('dog', 'Fido');
console.log(dog.name); // Fido
class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  notify() {
    this.observers.forEach(observer => observer.update());
  }
}

class Observer {
  update() {
    console.log('L\'état du sujet a changé');
  }
}

let subject = new Subject();
let observer = new Observer();

subject.addObserver(observer);
subject.notify(); // L'état du sujet a changé

Symboles

Symbol est un type primitif introduit en ES6. Les symboles sont uniques et immuables, souvent utilisés pour ajouter des propriétés aux objets de manière à éviter les collisions de noms.

const sym = Symbol('description');
let obj = {
  [sym]: 'valeur unique'
};
console.log(obj[sym]); // valeur unique

Opérateur Nullish Coalescing (??)

L’opérateur Nullish Coalescing (??) permet de retourner la première valeur non nulle ou définie parmi deux valeurs. Il est similaire à l’opérateur OR (||), mais diffère en ce qu’il ne remplace pas une valeur si celle-ci est 0 ou false.

let x = null;
let y = 5;

console.log(x ?? y); // 5

let a = 0;
let b = 5;
console.log(a ?? b); // 0 (ici, l'opérateur ?? ne remplace pas 0, contrairement à ||)

Optional Chaining (?.)

L’opérateur Optional Chaining (?.) permet d’accéder à une propriété d’un objet profondément imbriqué sans avoir à vérifier si chaque niveau existe. Si une référence est null ou undefined, l’expression renverra undefined au lieu de provoquer une erreur.

let person = { name: { firstName: 'John' } };

console.log(person?.name?.firstName); // John
console.log(person?.address?.city); // undefined

Boucles modernes (for...of, for...in)

JavaScript propose plusieurs types de boucles pour itérer sur des collections.

let arr = [1, 2, 3];
for (let value of arr) {
  console.log(value); // 1, 2, 3
}
let person = { name: 'Alice', age: 25 };
for (let key in person) {
  console.log(key, person[key]); // name Alice, age 25
}

Gestion avancée des erreurs avec try...catch

Le bloc try...catch est essentiel pour gérer les erreurs de manière robuste, en capturant les exceptions et en les traitant de manière appropriée.

try {
  let result = riskyOperation();
  console.log(result);
} catch (error) {
  console.error('Une erreur est survenue :', error);
} finally {
  console.log('Le bloc try/catch est terminé');
}