freedzhost.cloud

JavaScript module 2 : les morceaux difficiles, en douceur

Niveau DébutantTemps estimé 14 hÀ lire avant JavaScript module 1 : les fondamentaux, console en mainUn mot inconnu ? Lexique

Quatorze séances pour les notions qui séparent « je bidouille » de « je programme » : où vivent les variables, les fonctions qui se souviennent (closures), les trois méthodes qui transforment les tableaux (map, filter, reduce), la déstructuration, les erreurs bien élevées, le JSON et les dates. Chacune est d'abord expliquée avec un exemple minuscule, puis pratiquée jusqu'à ce qu'elle coule. Méthode : 20 minutes de théorie, 30 de pratique, 10 de synthèse.

Jour 15 — La portée : où vit une variable

Objectif : prévoir où une variable est visible, et comprendre le « masquage ».

Théorie (20 min)

La portée (scope) d'une variable est la zone du code où son nom est visible. Avec let et const, la règle est simple : une variable vit dans le bloc qui l'a vue naître — le bloc étant ce qui est entre accolades : une fonction, un if, une boucle.

const global = "partout";
function essai() {
  const locale = "ici seulement";
  if (true) {
    const plusFine = "dans le if";
    console.log(global, locale, plusFine);  // les trois sont visibles
  }
  console.log(global, locale);              // plusFine n'existe plus ici
}
// ici, ni locale ni plusFine n'existent

La lecture va de l'intérieur vers l'extérieur : dans un bloc, un nom inconnu est cherché dans le bloc parent, puis le grand-parent, jusqu'au haut du fichier — puis ReferenceError. Conséquence directe : le même nom peut exister à deux étages. let total = 0; en haut, puis let total = 5; dans une fonction : à l'intérieur, total désigne le 5, le 0 est masqué (shadowing). Légal, mais source de bugs : quand un nom est masqué, relisez deux fois.

Pourquoi c'est une bonne nouvelle : la portée par bloc signifie que deux fonctions peuvent chacune avoir leur total sans se marcher dessus, et qu'une variable de boucle for (const article of courses) disparaît avec la boucle. Les variables « qui traînent partout » sont un défaut de conception, pas un confort.

Sans exécuter : let x = 1; if (true) { let x = 2; } console.log(x);

  1. 1
  2. 2
  3. ReferenceError
Réponse

1 : le x du if est une autre variable, née et morte dans le bloc.

Pratique (30 min)

  1. Retapez l'exemple du cours dans portee.js, lancez-le, puis déplacez le console.log des trois variables hors du if : lisez la ReferenceError et notez quel nom manque.
  2. Écrivez deux fonctions compterA() et compterB() qui déclarent chacune let total = 0;, l'incrémentent et le renvoient. Appelez-les : chacune a SON total. Critère de réussite : les deux affichent 1, indépendamment.
  3. Créez le masquage : const unite = "cm"; en haut, puis dans une fonction const unite = "px"; et un console.log dans chaque zone. Prédisez, exécutez, notez.
  4. Testez la variable de boucle : for (const c of [1, 2]) {} puis console.log(c) — ReferenceError, la boucle a tout emporté.

Tips agent : « dans ce fichier, liste chaque variable avec la zone exacte où elle est visible » : un agent établit cette carte bien. Piège : il peut rater un masquage quand le fichier est long — donnez-lui le fichier entier, pas un extrait. Preuve : une ReferenceError provoquée exprès confirme la frontière.

Synthèse (10 min)

Trois lignes : la règle du bloc, la recherche vers l'extérieur, le masquage. Sans notes : écrivez un if qui déclare une variable et prouvez qu'elle n'existe pas après le if.

Jour 16 — Les closures : une fonction qui se souvient

Objectif : comprendre et écrire une closure : une fonction qui garde l'accès aux variables de son lieu de naissance.

Théorie (20 min)

Une fonction peut être renvoyée par une autre fonction. Et voici le fait qui fait la réputation du langage : la fonction renvoyée emporte avec elle les variables de la fonction qui l'a créée, même après que celle-ci a terminé. Ce paquet « fonction + ses variables souvenirs » s'appelle une closure.

function creerCompteur() {
  let total = 0;              // variable privée : inaccessible de l'extérieur
  return function () {
    total++;
    return total;
  };
}
const compteur = creerCompteur();
compteur();   // 1
compteur();   // 2
compteur();   // 3

Déroulez-le à la main, c'est le seul moyen de le sentir : creerCompteur() crée total, renvoie la petite fonction, et termine. Pourtant total n'est pas mort : la petite fonction le tient. Chaque appel le fait monter. Et total est privé : impossible d'y toucher depuis l'extérieur, seule la fonction y accède. C'est l'outil de base pour fabriquer de l'état protégé sans classes.

Deuxième exemple à connaître, la fabrique de fonctions :

const fois = n => x => x * n;
const triple = fois(3);
triple(5);   // 15

fois(3) renvoie une fonction qui « se souvient » que n vaut 3. Les trois quarts des closures que vous écrirez ressemblent à l'un de ces deux patrons : le compteur (état privé) ou la fabrique (fonction configurée).

Écrivez creerSalutation(formule) qui renvoie une fonction : appelée avec un nom, elle renvoie « formule nom ! ».

function creerSalutation(formule) {
}
Réponse
function creerSalutation(formule) {
  return function (nom) {
    return `${formule} ${nom} !`;
  };
}

Pratique (30 min)

  1. Retapez le compteur dans closure.js, lancez-le. Puis créez un DEUXIÈME compteur : const autre = creerCompteur(); Appelez les deux en alternance. Critère de réussite : chacun garde son propre total — deux closures, deux souvenirs.
  2. Prouvez la vie privée : compteur.total et toute lecture directe échouent (undefined). Le seul chemin vers total est un appel.
  3. Retapez la fabrique fois, créez double et fois10, testez.
  4. Réussissez l'exercice encadré creerSalutation.
  5. Dans le carnet, expliquez avec vos mots pourquoi total survit à la fin de creerCompteur.

Tips agent : « explique cette closure ligne par ligne, puis prédis les cinq prochains appels » : bon exercice de vérification croisée. Piège : un agent peut décrire le mécanisme correctement et se tromper sur la valeur du cinquième appel. Preuve : vous exécutez les cinq appels et vous comparez.

Synthèse (10 min)

Trois lignes : ce qu'une closure emporte, les deux patrons (compteur, fabrique), la vie privée offerte. Sans notes : réécrivez creerCompteur de mémoire et montez à 3.

Jour 17 — map : transformer une liste

Objectif : transformer chaque élément d'un tableau avec map, sans boucle écrite à la main.

Théorie (20 min)

map applique une fonction à chaque élément d'un tableau et renvoie un nouveau tableau des résultats, de même longueur :

const prix = [10, 20, 30];
const ttc = prix.map(p => p * 1.2);
// [12, 24, 36]

Lisez prix.map(p => p * 1.2) comme : « pour chaque p de prix, rends p × 1.2 ». La fonction passée en argument est appelée par map, une fois par élément, avec l'élément courant. C'est une fonction d'ordre supérieur : une fonction qui prend une fonction en paramètre. Vous avez vu au jour 12 qu'une fonction est une valeur ; map est la première grande utilité de ce fait.

Trois choses à graver : map ne modifie pas le tableau d'origine (vérifiez : prix est intact après) ; le résultat a toujours la même longueur que l'entrée ; la fonction reçoit aussi l'index en deuxième paramètre (prix.map((p, i) => ...)) pour les rares cas où la position compte. Si vous écrivez une boucle qui fait resultat.push(transforme(element)), elle peut devenir un map.

Écrivez initiales(noms) qui, pour un tableau de noms complets comme "Ada Lovelace", renvoie les initiales "AL". Indice : map, split(" "), et les premières lettres.

function initiales(noms) {
}
Réponse
function initiales(noms) {
  return noms.map(nom => nom.split(" ").map(mot => mot[0]).join(""));
}

Pratique (30 min)

  1. Dans le REPL : [1, 2, 3, 4].map(n => n ** 2), puis ["ada", "grace"].map(s => s.toUpperCase()). Vérifiez à chaque fois que le tableau d'origine est intact.
  2. Avec const panier = [{ nom: "pommes", prix: 3 }, { nom: "riz", prix: 2 }];, produisez le tableau des prix seuls : panier.map(a => a.prix) → [3, 2]. Puis le tableau des noms.
  3. Transformez le panier en phrases : panier.map(a => `${a.nom} : ${a.prix} €`). Critère de réussite : ["pommes : 3 €", "riz : 2 €"].
  4. Utilisez l'index : ["a", "b", "c"].map((lettre, i) => `${i + 1}. ${lettre}`).
  5. Réussissez l'exercice encadré initiales.

Tips agent : « réécris cette boucle for avec map » : un agent fait cette conversion très bien. Piège : il peut glisser un filter ou un forEach en même temps « pour optimiser » — exigez la conversion minimale. Preuve : le nouveau code produit un tableau identique sur vos données.

Synthèse (10 min)

Trois lignes : ce que map prend et ce qu'il rend, l'immuabilité du tableau source, le paramètre index. Sans notes : transformez [2, 4, 6] en [1, 2, 3] avec un seul map.

Jour 18 — filter : garder ce qui compte

Objectif : sélectionner les éléments d'un tableau avec filter et un prédicat.

Théorie (20 min)

filter parcourt un tableau et ne garde que les éléments pour lesquels votre fonction renvoie true. La fonction s'appelle un prédicat : elle prend un élément, répond vrai ou faux.

const ages = [12, 25, 17, 34];
const majeurs = ages.filter(age => age >= 18);
// [25, 34]

Le résultat a au plus la longueur de l'entrée — de zéro élément à tous. Comme map, filter ne touche pas l'original. Et comme map, il remplace une boucle : si vous écrivez if (condition) resultat.push(element) dans un for, c'est un filter déguisé.

La combinaison naturelle est map APRÈS filter : d'abord garder ce qui compte, puis le transformer :

const panier = [
  { nom: "pommes", prix: 3 },
  { nom: "café", prix: 12 },
  { nom: "riz", prix: 2 }
];
const chers = panier.filter(a => a.prix > 5).map(a => a.nom);
// ["café"]

Le chaînage se lit de gauche à droite comme un tapis roulant : le tableau entre, chaque méthode en renvoie un autre. On le pratiquera en entier au jour 25 ; aujourd'hui, l'essentiel est le prédicat bien écrit : une expression booléenne claire, sans effet de bord.

Écrivez pairs(nombres) qui ne garde que les nombres pairs.

function pairs(nombres) {
}
Réponse
function pairs(nombres) {
  return nombres.filter(n => n % 2 === 0);
}

Pratique (30 min)

  1. Dans le REPL : [5, 12, 8, 130, 44].filter(n => n > 10). Vérifiez que l'original est intact.
  2. Avec le panier du cours, gardez les articles à moins de 5 €, puis produisez leurs noms en majuscules (filter puis map).
  3. Filtrez ["ada", "", "grace", ""] pour éliminer les chaînes vides — rappelez-vous le falsy du jour 9 : .filter(s => s.length > 0), ou plus court .filter(Boolean) ? Testez les deux et expliquez pourquoi le second marche.
  4. Critère de réussite : pour le panier, vous obtenez ["café"] pour « plus de 5 € » et ["POMMES", "RIZ"] pour « moins de 5 € en majuscules ».
  5. Réussissez l'exercice encadré pairs.

Tips agent : « voici mon tableau d'objets et la liste attendue ; écris le filter » : avec les données réelles collées, un agent vise juste. Piège : sans l'extrait de données, il invente les noms de clés. Preuve : le résultat comparé élément par élément à la liste attendue.

Synthèse (10 min)

Trois lignes : ce qu'est un prédicat, la différence map/filter, l'ordre filter-puis-map. Sans notes : de [3, 10, 7, 20], gardez les > 8 puis doublez-les : [20, 40].

Jour 19 — reduce : tout ramener à une valeur

Objectif : plier un tableau en une seule valeur avec reduce, sans se tromper de valeur initiale.

Théorie (20 min)

reduce est le plus puissant des trois et le moins intuitif : il plie un tableau en une seule valeur (un nombre, un objet, un autre tableau…) en faisant circuler un accumulateur d'élément en élément.

const prix = [10, 20, 30];
const total = prix.reduce((acc, p) => acc + p, 0);
// 60

Déroulez : l'accumulateur acc part de la valeur initiale (0, deuxième argument de reduce). Premier élément : acc=0, p=10, la fonction rend 10, qui devient le nouvel acc. Deuxième : acc=10, p=20 → 30. Troisième : acc=30, p=30 → 60. Le dernier acc est le résultat.

Deux pièges dominent. Premier : oublier la valeur initiale. Sans elle, reduce prend le premier élément comme acc de départ — et sur un tableau vide, il lève une TypeError. Mettez toujours la valeur initiale ; elle dit aussi la forme du résultat (0 pour une somme, {} pour un comptage, [] pour une liste). Deuxième : la fonction doit renvoyer l'accumulateur à chaque tour ; un oubli de return et tout devient undefined.

// compter les occurrences : la valeur initiale est un objet vide
const mots = ["a", "b", "a"];
const compte = mots.reduce((acc, mot) => {
  acc[mot] = (acc[mot] ?? 0) + 1;
  return acc;
}, {});
// { a: 2, b: 1 }

Écrivez compter(elements) qui renvoie un objet comptant les occurrences de chaque élément, avec reduce.

function compter(elements) {
}
Réponse
function compter(elements) {
  return elements.reduce((acc, el) => {
    acc[el] = (acc[el] ?? 0) + 1;
    return acc;
  }, {});
}

Pratique (30 min)

  1. Dans le REPL, refaites la somme du cours en la déroulant dans le carnet, colonne par colonne (acc, p, retour). Puis changez la valeur initiale à 100 : 160. Comprenez pourquoi.
  2. Calculez le maximum de [4, 18, 7, 12] avec reduce : (acc, n) => n > acc ? n : acc, valeur initiale… laquelle ? Réfléchissez au piège si tous les nombres sont négatifs.
  3. Provoquez la TypeError : [].reduce((a, b) => a + b) sans valeur initiale. Lisez le message, il dit tout.
  4. Critère de réussite : votre comptage d'occurrences sur ["a", "b", "a"] donne { a: 2, b: 1 }, et sur [] donne {} sans erreur.
  5. Réussissez l'exercice encadré compter.

Tips agent : « déroule ce reduce tour par tour dans un tableau à trois colonnes » : excellent pour vérifier votre propre déroulé. Piège : sur un accumulateur-objet, l'agent peut sauter une étape. Preuve : un console.log(acc) dans la fonction, exécuté par vous, montre chaque tour.

Synthèse (10 min)

Trois lignes : l'accumulateur, la valeur initiale et son double rôle, le return obligatoire. Sans notes : écrivez le produit de [2, 3, 4] avec reduce → 24.

Jour 20 — Déstructuration, spread et rest

Objectif : extraire des valeurs d'objets et de tableaux en une ligne, et utiliser les trois points dans les deux sens.

Théorie (20 min)

La déstructuration extrait des propriétés d'un objet (ou des éléments d'un tableau) directement dans des variables :

const livre = { titre: "1984", auteur: "Orwell", pages: 328 };
const { titre, auteur } = livre;
// titre vaut "1984", auteur vaut "Orwell" — deux const en une ligne

const [premier, troisieme] = ["a", "b", "c"];
// premier: "a", troisieme: "b" — les tableaux se déstructurent par position

Sur les objets, les noms doivent correspondre aux clés ; on peut renommer (const { titre: t } = livre;) et donner un défaut (const { note = 0 } = livre; — le défaut ne s'applique que si la valeur est undefined). La forme qui compte le plus en pratique : les paramètres déstructurés — function afficher({ titre, auteur }) reçoit un objet et n'en extrait que ce qui sert. Fini les options.a, options.b, options.c en début de fonction.

Les trois points ... ont deux visages. En spread (étalement), ils déballent : const copie = { ...livre, pages: 400 }; crée un nouvel objet avec tout livre, sauf pages remplacé — LA façon moderne de « modifier » sans muter. En rest (reste), ils ramassent : const [tete, ...queue] = [1, 2, 3]; donne tete=1, queue=[2,3]. Même syntaxe, deux directions : étaler à la construction, ramasser à la réception.

Que vaut const { a = 5 } = { a: 0 }; a ?

  1. 5
  2. undefined
  3. 0
Réponse

0 : la valeur par défaut ne s'applique que si la propriété est undefined — 0 est une vraie valeur.

Pratique (30 min)

  1. Déstructurez const user = { nom: "Ada", ville: "Londres", admin: true }; en trois variables, puis en renommant nom en pseudo, puis avec un défaut langue = "fr".
  2. Écrivez function etiquette({ nom, ville }) qui renvoie « nom (ville) » et appelez-la avec user.
  3. Faites une « modification sans mutation » : const user2 = { ...user, admin: false }; Vérifiez que user.admin est toujours true. Critère de réussite : user est intact, user2 a admin à false.
  4. Étalez deux tableaux en un : [...[1, 2], ...[3, 4]]. Puis ramassez : const [a, ...reste] = [1, 2, 3, 4];
  5. Piège à connaître : la copie par spread est superficielle — les sous-objets restent partagés. Vérifiez : const o = { p: { x: 1 } }; const c = { ...o }; c.p.x = 9; o.p.x; donne 9.

Tips agent : « réécris cette fonction pour prendre un objet d'options déstructuré » : un agent fait cette modernisation bien. Piège : il peut déstructurer trop tôt et casser les appels existants. Preuve : les anciens appels et les nouveaux donnent le même résultat — testez les deux.

Synthèse (10 min)

Trois lignes : déstructuration par clé vs par position, défaut seulement si undefined, spread étale / rest ramasse. Sans notes : échangez deux variables avec la déstructuration ([a, b] = [b, a]) et copiez un objet en changeant une clé.

Jour 21 — Les erreurs : throw, try…catch

Objectif : lever une erreur soi-même, l'attraper proprement, et savoir quand NE PAS attraper.

Théorie (20 min)

Jusqu'ici, les erreurs vous arrivaient dessus. Aujourd'hui vous en créez : throw interrompt l'exécution et remonte une erreur, try…catch l'attrape au niveau qui sait quoi en faire.

function diviser(a, b) {
  if (b === 0) {
    throw new Error("division par zéro");
  }
  return a / b;
}

try {
  const r = diviser(10, 0);
  console.log(r);
} catch (err) {
  console.log("erreur attrapée :", err.message);
}

La mécanique : throw fait remonter l'erreur de fonction en fonction jusqu'au premier catch — ou jusqu'au crash si personne ne l'attrape. Le bloc finally, optionnel, s'exécute dans tous les cas (utile pour fermer un fichier, rétablir un état). On lève toujours un Error (new Error("...")), pas une chaîne : l'Error emporte la pile d'appels, inestimable au débogage.

La discipline qui distingue le bon code : lancer tôt, attraper tard. La fonction qui détecte l'impossibilité (paramètre invalide, donnée manquante) lance immédiatement, avec un message qui dit ce qu'elle attendait et ce qu'elle a reçu. L'attrape se fait au niveau qui peut décider : afficher un message à l'utilisateur, réessayer, abandonner proprement. Attraper pour « faire taire » (catch { } vide) est le pire réflexe : il transforme un bug visible en bug invisible. Un programme qui échoue en silence est bien pire qu'un programme qui plante en parlant.

Écrivez convertir(texte) qui renvoie Number(texte), mais lance un Error avec le message « pas un nombre » si le résultat est NaN. (Le troisième test vérifie juste que la chaîne vide ne plante pas.)

function convertir(texte) {
}
Réponse
function convertir(texte) {
  const n = Number(texte);
  if (Number.isNaN(n)) {
    throw new Error("pas un nombre");
  }
  return n;
}

Pratique (30 min)

  1. Retapez diviser dans erreurs.js, appelez-la avec 0 hors try : lisez le crash et sa pile. Puis dans un try…catch : le programme survit et affiche le message.
  2. Critère de réussite : node erreurs.js affiche « erreur attrapée : division par zéro » et se termine sans pile d'erreur.
  3. Ajoutez un finally qui affiche « fin du traitement » : constatez qu'il s'exécute avec ET sans erreur.
  4. Écrivez le mauvais réflexe puis réparez-le : un catch vide autour d'un code qui plante, constatez le silence, remettez le console.log de l'erreur.
  5. Réussissez l'exercice encadré convertir.

Tips agent : « quelles erreurs cette fonction devrait-elle lancer, avec quels messages ? » : un agent énumère bien les cas limites. Piège : il peut enrober tout le corps d'un try « par sécurité ». Preuve : pour chaque throw proposé, écrivez l'appel qui le déclenche — si vous n'y arrivez pas, le cas n'existe pas.

Synthèse (10 min)

Trois lignes : throw remonte jusqu'au catch, pourquoi new Error et pas une chaîne, lancer tôt / attraper tard. Sans notes : écrivez une fonction age(x) qui lance si x est négatif, et l'appel qui l'attrape.

Jour 22 — JSON : le format d'échange du web

Objectif : convertir entre objets JavaScript et texte JSON, et connaître les limites du format.

Théorie (20 min)

JSON (JavaScript Object Notation) est le format texte dans lequel le web échange des données : réponses d'API, fichiers de configuration, sauvegardes. Il ressemble à un objet JavaScript, mais c'est du texte, avec des règles strictes : clés entre guillemets doubles obligatoires, pas de virgule finale, pas de commentaires, pas de fonctions.

const objet = { nom: "Ada", age: 36 };
const texte = JSON.stringify(objet);
// '{"nom":"Ada","age":36}' — une chaîne, stockable, envoyable
const relu = JSON.parse(texte);
// { nom: "Ada", age: 36 } — un vrai objet, de nouveau manipulable

stringify sérialise (objet → texte), parse désérialise (texte → objet). Deux détails pratiques de stringify : un deuxième argument à null et un troisième à 2 donnent un texte indenté, lisible par des humains : JSON.stringify(objet, null, 2). Et parse est violent : un texte malformé lève une SyntaxError — sur une donnée venue d'ailleurs, parse se met dans un try.

Les limites à connaître : JSON ne sait pas représenter undefined (la propriété disparaît), les fonctions (ignorées), les dates (stringify les convertit en chaîne ISO, parse ne les re-transforme pas — vous récupérez du texte), NaN et Infinity (deviennent null). Quand une donnée « perd un morceau » après un aller-retour JSON, c'est presque toujours l'un de ces cas.

Que contient JSON.stringify({ a: undefined, b: 1 }) ?

  1. '{"a":undefined,"b":1}'
  2. '{"b":1}'
  3. une erreur
Réponse

La propriété a disparaît : JSON n'a pas de undefined.

Pratique (30 min)

  1. Dans le REPL, faites l'aller-retour complet : un objet avec chaîne, nombre, booléen, tableau, sous-objet → stringify → parse → vérifiez une propriété profonde. JSON.stringify(relu) === texte doit valoir true.
  2. Critère de réussite : relu n'est pas le même objet que objet (c'est une copie reconstruite) mais s'y compare parfaitement : relu.nom === objet.nom.
  3. Produisez la version indentée avec le troisième argument, et affichez-la : c'est le format de votre futur package.json.
  4. Provoquez l'échec : JSON.parse("{nom: 'Ada'}") (guillemets simples, clé sans guillemets) — SyntaxError. Attrapez-la dans un try.
  5. Observez les pertes : stringify un objet contenant une Date, undefined, NaN et une fonction ; parsez ; listez ce qui a changé dans le carnet.

Tips agent : collez une vraie réponse d'API tronquée et demandez « est-ce du JSON valide, et quel chemin mène au champ X ? ». Piège : l'agent peut « réparer » du JSON invalide sans vous le dire — exigez qu'il signale toute modification. Preuve : JSON.parse exécuté chez vous tranche.

Synthèse (10 min)

Trois lignes : stringify/parse, les règles strictes du texte JSON, les quatre pertes au passage (undefined, fonctions, dates, NaN). Sans notes : sérialisez un objet, affichez-le indenté, relisez-le.

Jour 23 — Les dates : Date et Intl

Objectif : créer, comparer et formater des dates sans douleur — et sans réinventer le calendrier.

Théorie (20 min)

Les dates sont le sujet glissant de tous les langages. JavaScript offre Date : un objet qui représente UN instant, stocké en millisecondes depuis le 1er janvier 1970 UTC (le « timestamp Unix »).

const maintenant = new Date();
const noel = new Date("2026-12-25");
maintenant.getTime();          // millisecondes depuis 1970 — comparable, calculable
noel > maintenant;             // les comparaisons fonctionnent sur les instants
maintenant.toISOString();      // "2026-11-04T10:23:45.123Z" — la forme texte standard

Trois règles de survie. (1) Stockez et comparez des timestamps ou des ISO, jamais des chaînes locales : "04/11/2026" veut dire le 4 novembre en France et le 11 avril aux États-Unis. (2) Les mois de getMonth() comptent de 0 à 11 — janvier est 0, l'erreur classique. (3) N'écrivez jamais un calcul de calendrier à la main (années bissextiles, fuseaux, heure d'été) : les cas limites vous battront.

Pour afficher une date à un humain, Intl.DateTimeFormat fait le travail proprement, avec la langue et le fuseau en paramètres :

const fmt = new Intl.DateTimeFormat("fr-FR", { dateStyle: "long" });
fmt.format(new Date("2026-12-25"));
// "25 décembre 2026"

Les différences se calculent en millisecondes puis se convertissent : (b - a) / 86_400_000 donne des jours (le séparateur _ dans les nombres longs est légal et bienvenu).

Pratique (30 min)

  1. Dans le REPL : créez la date d'aujourd'hui, affichez son timestamp avec getTime, son ISO avec toISOString, et sa version française avec Intl.DateTimeFormat.
  2. Calculez votre âge en jours : timestamp d'aujourd'hui moins timestamp de votre naissance, divisé par 86_400_000, arrondi avec Math.floor.
  3. Critère de réussite : le résultat est cohérent avec un calcul en ligne ; et new Date("2026-01-15").getMonth() vous a bien donné 0 — notez-le.
  4. Comparez : quelle est la plus ancienne de deux dates données ? Écrivez la fonction plusAncienne(a, b) qui renvoie la plus petite.
  5. Formatez la même date en "fr-FR", "en-US" et "ja-JP" : trois écritures, un seul instant.

Tips agent : « formate ce timestamp pour un lecteur français » : l'agent connaît Intl. Piège : il peut inventer des options Intl qui n'existent pas. Preuve : exécutez — une option invalide lève une RangeError immédiate, c'est sans pitié et c'est tant mieux.

Synthèse (10 min)

Trois lignes : timestamp vs affichage, getMonth de 0 à 11, pourquoi Intl et pas de formatage à la main. Sans notes : affichez la date du jour en français long, puis le nombre de jours jusqu'au 1er janvier prochain.

Jour 24 — find, some, every, sort : les autres couteaux suisses

Objectif : compléter la boîte à outils des tableaux : trouver, tester, trier.

Théorie (20 min)

Au-delà du trio map/filter/reduce, quatre méthodes répondent aux questions quotidiennes :

  • find(predicat) : le PREMIER élément qui passe le prédicat, ou undefined. users.find(u => u.id === 3) — la recherche par clé, celle que vous écrirez mille fois.
  • some(predicat) : true si AU MOINS un élément passe. ages.some(a => a < 18) — « y a-t-il un mineur ? »
  • every(predicat) : true si TOUS passent. ages.every(a => a >= 0) — « toutes les valeurs sont-elles saines ? »
  • sort(comparateur) : trie le tableau… en place (il modifie l'original !) et, sans comparateur, par ordre ALPHABÉTIQUE de chaînes : [10, 9, 80].sort() donne [10, 80, 9]. Le comparateur correct pour des nombres : (a, b) => a - b (croissant) ou (a, b) => b - a. Pour éviter la mutation : copiez d'abord, [...t].sort(...), ou utilisez toSorted (Node 20+, navigateurs récents).

Et deux parents pauvres utiles : includes(valeur) pour les tableaux de valeurs simples (jour 6), flat() qui aplatit les tableaux de tableaux d'un niveau, et flatMap(f) qui fait un map puis un flat d'un coup — pratique quand chaque élément produit une petite liste.

Que donne [40, 1, 5, 200].sort() ?

  1. [1, 5, 40, 200]
  2. [1, 200, 40, 5]
  3. [200, 40, 5, 1]
Réponse

Tri alphabétique : "1" < "200" < "40" < "5". Le comparateur (a, b) => a - b est obligatoire pour des nombres.

Pratique (30 min)

  1. Avec const users = [{ id: 1, nom: "Ada" }, { id: 2, nom: "Grace" }];, trouvez l'utilisateur 2 avec find, vérifiez l'absence du 99 (undefined, pas d'erreur).
  2. Testez some et every sur [3, 7, 21] : « tous impairs ? » (every, true), « au moins un pair ? » (some, false).
  3. Reproduisez le piège du tri alphabétique, réparez avec le comparateur, puis avec toSorted sur une copie — vérifiez que l'original est intact.
  4. Critère de réussite : [40, 1, 5, 200].toSorted((a, b) => a - b) donne [1, 5, 40, 200] et l'original n'a pas bougé.
  5. flatMap : ["a b", "c d"].flatMap(s => s.split(" ")) → ["a", "b", "c", "d"].

Tips agent : « quelle méthode de tableau pour [besoin décrit en une phrase] ? » : map/filter/find/some/every/reduce, l'agent connaît la carte. Piège : il peut proposer findIndex ou reduce là où some suffit — demandez « le plus simple ». Preuve : l'exemple minimal exécuté dans la console.

Synthèse (10 min)

Trois lignes : find rend un élément ou undefined, some/every rendent un booléen, sort mute ET trie en texte par défaut. Sans notes : triez [30, 4, 21] correctement des deux façons (avec copie).

Jour 25 — Chaîner les méthodes : un pipeline de données

Objectif : composer map, filter, reduce en pipelines lisibles, et savoir quand s'arrêter.

Théorie (20 min)

Chaque méthode de tableau renvoie un tableau (sauf reduce, find, some, every), donc elles s'enchaînent : la sortie de l'une est l'entrée de la suivante. C'est un pipeline, et c'est le style JavaScript moderne pour traiter des collections :

const ventes = [
  { produit: "café", qte: 2, prix: 12 },
  { produit: "thé", qte: 1, prix: 8 },
  { produit: "café", qte: 3, prix: 12 }
];
const caCafe = ventes
  .filter(v => v.produit === "café")
  .map(v => v.qte * v.prix)
  .reduce((acc, montant) => acc + montant, 0);
// 60

Lecture : une ligne par étape, chaque étape ne fait qu'une chose, le nom du résultat dit ce qui sort. Trois disciplines : (1) mettez le filter AVANT le map quand c'est possible — inutile de transformer ce qu'on va jeter ; (2) chaque fonction fléchée reste courte et sans effet de bord — si elle dépasse deux lignes, donnez-lui un nom et sortez-la ; (3) sachez vous arrêter : un pipeline de huit étapes illisible vaut moins que trois variables intermédiaires bien nommées.

Note de coût honnête : chaque étape crée un tableau intermédiaire. Pour les collections de ce parcours (quelques centaines d'éléments), c'est négligeable — privilégiez la lisibilité. Le jour où vous traitez des millions d'éléments, vous réécrirez en boucle ; ce jour-là, vous saurez pourquoi.

Pratique (30 min)

  1. Retapez l'exemple des ventes, lancez-le : 60. Puis inversez filter et map (map d'abord sur les montants) — constatez que ça marche encore mais que le filter n'a plus accès au produit : l'ordre porte le sens.
  2. Sur [" Ada ", "", " grace", "hopper "] : nettoyez (trim), jetez les vides, mettez en majuscules, en trois étapes chaînées. Résultat : ["ADA", "GRACE", "HOPPER"].
  3. À partir de [{ nom: "a", tags: ["x", "y"] }, { nom: "b", tags: ["z"] }], produisez la liste plate des tags avec flatMap.
  4. Critère de réussite : les trois pipelines rendent exactement les résultats annoncés, et chaque pipeline se lit à voix haute comme une phrase.
  5. Repérez dans vos fichiers du module 1 une boucle for…of qui construit un tableau : réécrivez-la en pipeline.

Tips agent : « réécris ce traitement en pipeline filter/map/reduce, une étape par ligne » : un agent excelle à cette mise en forme. Piège : il peut fusionner deux étapes « pour la performance » et tuer la lisibilité. Preuve : mêmes données en entrée, même résultat en sortie, et chaque ligne fait une seule chose.

Synthèse (10 min)

Trois lignes : le chaînage et son sens de lecture, filter avant map, quand préférer des variables intermédiaires. Sans notes : écrivez le pipeline « prix TTC des articles à moins de 10 €, puis leur somme ».

Jour 26 — Mini-projet : le tableau de scores

Objectif : assembler objets, tableaux et pipelines dans un analyseur de résultats complet.

Théorie (20 min)

Le projet du jour : un tableau de scores de joueurs, et quatre questions à y répondre par le code. C'est la forme exacte de 80 % du JavaScript réel : une liste d'objets, des questions, des pipelines.

const joueurs = [
  { nom: "Ada", points: [12, 18, 7] },
  { nom: "Grace", points: [20, 15] },
  { nom: "Alan", points: [5, 9, 11, 3] }
];

La méthode de travail compte autant que le résultat : une question à la fois, une fonction par question, chaque fonction vérifiée sur les données AVANT de passer à la suivante. Les quatre questions : le total de chaque joueur, le classement décroissant, le meilleur score individuel de la partie, la moyenne générale. Chacune combine des outils vus : map + reduce pour les totaux, toSorted pour le classement, flatMap + reduce pour le record.

Structure du fichier : les données en haut, les fonctions au milieu, les quatre console.log de présentation en bas. Et pour le classement, rappelez-vous le jour 24 : toSorted et un comparateur, jamais sort tout court.

Pratique (30 min)

  1. Créez scores.js avec les données du cours. Écrivez totalDe(joueur) : la somme de ses points avec reduce. Vérifiez : Ada → 37.
  2. totaux() : map sur les joueurs, chaque élément devient { nom, total }. Vérifiez les trois.
  3. classement() : totaux() trié par total décroissant avec toSorted. Vérifiez : Ada, Grace, Alan.
  4. record() : le plus grand nombre parmi tous les points (flatMap puis reduce, ou Math.max avec spread). Vérifiez : 20.
  5. moyenneGenerale() : somme de tous les points divisée par leur nombre. Vérifiez : 100 / 9 ≈ 11,11.
  6. Critère de réussite : les cinq fonctions affichent leurs résultats en bas du fichier, et les chiffres collent à la main.

Tips agent : « voici mes données et mes cinq résultats calculés à la main ; écris les fonctions qui les reproduisent » : donner les résultats attendus transforme l'agent en vérificateur. Piège : sans ces valeurs témoins, un code faux mais plausible passe. Preuve : les cinq chiffres, comparés un par un.

Synthèse (10 min)

Trois lignes : la méthode une-question-une-fonction, le couple map+reduce pour agréger, pourquoi toSorted. Sans notes : réécrivez totalDe et classement de mémoire.

Jour 27 — Révisions guidées

Objectif : refaire les gestes clés du module 2, de mémoire, avec la console comme seul juge.

Théorie (20 min)

Pas de notion nouvelle. Relisez votre carnet des jours 15 à 26 et listez ce qui vous a résisté : pour la plupart, ce sera la closure (jour 16) et le reduce (jour 19) — les deux boss du module. Relisez ces deux séances en entier si leurs exemples ne vous reviennent pas.

Le test honnête du module : pouvez-vous, sans notes, (1) écrire une fabrique de fonctions, (2) dérouler un reduce sur trois éléments, (3) transformer une liste d'objets en réponse à une question métier (« les noms des articles en stock, par prix croissant ») ? Ces trois gestes couvrent 90 % du JavaScript applicatif.

Sans exécuter : [1, 2, 3].map(n => n * 2).filter(n => n > 2).reduce((a, b) => a + b, 0)

  1. 6
  2. 10
  3. [4, 6]
Réponse

10 : map donne [2, 4, 6], filter garde [4, 6], reduce plie en 10. Déroulez toujours étape par étape.

Pratique (30 min)

  1. Réécrivez de mémoire : le compteur-closure (jour 16), le comptage d'occurrences par reduce (jour 19), le pipeline des ventes (jour 25). Exécutez chacun.
  2. Critère de réussite : les trois tournent du premier ou deuxième coup. Notez ce qui a coincé — c'est votre programme de relecture.
  3. Exercice frais : const mots = ["chat", "chien", "rat", "poisson"]; — produisez, en un pipeline, la longueur totale des mots de plus de trois lettres. (Réponse : 16.)
  4. Exercice frais : à partir d'un tableau d'objets { ville, temperature } que vous inventez (5 villes), sortez les noms des villes au-dessus de 15°, en majuscules, par ordre alphabétique.
  5. Prédiction puis vérification : const f = n => g => n + g; const ajoute5 = f(5); ajoute5(10) — écrivez la réponse d'abord.

Tips agent : « génère cinq exercices de prédiction sur map/filter/reduce avec leurs réponses dans un détails repliable » : un agent est une bonne machine à quiz. Piège : vérifiez chaque réponse proposée dans la console — une sur vingt est fausse. Preuve : la console, toujours la console.

Synthèse (10 min)

Trois lignes : les deux notions re-travaillées, votre taux de réussite aux exercices de mémoire, ce qui reste fragile. Demain, bilan du module.

Jour 28 — Bilan du module 2

Objectif : valider le module 2 et préparer le grand virage : l'asynchrone.

Théorie (20 min)

Auto-test oral du module : expliquez chacune de ces phrases comme à un ami : « une fonction emporte les variables de son lieu de naissance », « map transforme, filter garde, reduce plie », « la valeur initiale dit la forme du résultat », « sort trie en texte et mute l'original », « stringify oublie undefined et les fonctions », « lancer tôt, attraper tard ». Chaque phrase floue renvoie à sa séance.

Le module 3 change d'étage. Jusqu'ici, chaque ligne finissait avant que la suivante commence — le code synchrone. Mais un programme réel attend : le réseau, un fichier, un minuteur. JavaScript a une réponse élégante et déroutante à cette attente : il ne bloque jamais, il repasse plus tard. C'est la boucle d'événements, et les promesses sont sa monnaie. Tout ce que vous avez appris sur les fonctions-valeurs (jours 12 et 16) devient l'outil principal : une fonction qu'on donne pour qu'on nous rappelle.

Pratique (30 min)

  1. Refaites les quatre exercices encadrés du module (creerSalutation, initiales, pairs, compter) sans rouvrir les réponses : c'est la mesure honnête.
  2. Critère de réussite : quatre feux verts. En dessous, la séance fautive est à refaire avant le module 3.
  3. Examen blanc minuteur (20 min) : const commandes = [{ client: "A", montants: [10, 5] }, { client: "B", montants: [20] }, { client: "C", montants: [] }]; Écrivez les fonctions : total par client, clients actifs (total > 0), chiffre d'affaires global, client le plus dépensier. Réponses : A:15, B:20, C:0 ; actifs A et B ; CA 35 ; client B.
  4. Rangez le module : dossier js/module-2/, carnet à jour.

Tips agent : « relis mon scores.js et trouve trois améliorations de lisibilité, sans changer les résultats » : la relecture est le meilleur usage d'un agent à ce stade. Piège : il peut renommer selon ses goûts — les noms sont les vôtres. Preuve : les cinq résultats inchangés après application.

Synthèse (10 min)

Trois lignes : les trois notions les plus solides, l'examen blanc réussi ou non, ce que vous attendez de l'asynchrone. Repos mérité : le module 3 est le plus étonnant du parcours.

Utile ? Dites-nous ce qu'il faut mesurer ensuite.

Commentaires

Ni compte ni traceur : un commentaire est lu par un humain avant d'apparaître.