Évaluation 1 — Corrigé
2025-2026 · séances 1 à 3
Le sujet correspondant : Évaluation 1 — Sujet.
Chaque réponse est repliée. Traitez d’abord la question, puis dépliez pour comparer. Un corrigé lu avant l’effort ne vous apprend presque rien.
Partie 1 — Questions théoriques
Barème : 10 points (1,5 point par question, sauf Question 6 : 2,5 points)
a) Affectations (0,5 pt)
| Ligne | Résultat | Explication |
|---|---|---|
c = "test" |
Refusée | c est déclaré avec let c = 42. TypeScript utilise l’inférence de type : comme 42 est un number, TypeScript déduit que c est de type number. On ne peut donc pas lui affecter un string. |
d = 100 |
Acceptée | d est de type number \| string (union type), ce qui signifie qu’il peut contenir soit un number, soit un string. 100 est un number, donc c’est valide. |
d = true |
Refusée | true est un boolean, qui n’est ni number ni string. Le type union number \| string n’inclut pas boolean. |
b) parseInt(“abc”) (0,5 pt)
evautNaN(Not a Number) à l’exécutione + 1vautNaN(toute opération avecNaNdonneNaN)- TypeScript ne détecte pas le problème car
parseIntest typé(string) => number— elle retourne toujours unnumberdu point de vue du système de types (etNaNest techniquement de typenumberen JavaScript) - Limite du typage statique : le système de types vérifie la forme du code (types corrects) mais ne peut pas prédire les valeurs à l’exécution. Certaines erreurs logiques échappent au compilateur.
c) Le danger de any (0,5 pt)
Exemple :
let x: any = "bonjour";
x = 42;
console.log(x.toUpperCase()); // Compile OK mais plante : x.toUpperCase is not a functionanydésactive complètement la vérification de types → les erreurs ne sont découvertes qu’à l’exécution- L’alternative est
unknown: il accepte n’importe quelle valeur en affectation, mais oblige à vérifier le type avant utilisation :
let x: unknown = 42;
if (typeof x === "string") {
console.log(x.toUpperCase()); // OK, TypeScript sait que x est string ici
}Grille de notation Q1
| Critère | Points |
|---|---|
| Identifie correctement les 3 affectations + explique l’inférence et le type union | 0,5 pt |
| Explique NaN, teste, et identifie les limites du typage statique | 0,5 pt |
| Fournit un exemple any qui plante + mentionne unknown comme alternative sûre | 0,5 pt |
a) Comportement de innerHTML (0,5 pt)
Le navigateur interprète le texte comme du HTML. La balise <img src="x"> est créée dans le DOM. Comme src="x" n’est pas une image valide, l’événement onerror se déclenche et exécute document.title='Piraté'. Le titre de la page change. C’est une démonstration d’injection de code — un attaquant pourrait exécuter du JavaScript arbitraire dans le contexte de la page.
b) Correction (0,5 pt)
Remplacer innerHTML par textContent :
affichage.textContent = valeur;textContent traite tout comme du texte brut. Le HTML n’est pas interprété, les balises s’affichent littéralement comme du texte. Aucun code JavaScript ne peut être exécuté.
c) Astro et la transpilation (0,5 pt)
Un navigateur ne comprend que le JavaScript. Dans un fichier .html classique, il faut donc référencer un .js qui a été transpilé au préalable via tsc.
Astro dispose d’un serveur de développement qui intercepte les requêtes. Quand le navigateur demande le fichier .ts, le serveur Astro le transpile automatiquement en JavaScript à la volée et sert le résultat. C’est transparent pour le développeur.
Grille de notation Q2
| Critère | Points |
|---|---|
| Teste et décrit correctement l’exécution du code JS injecté via innerHTML | 0,5 pt |
| Propose textContent et explique pourquoi c’est sûr | 0,5 pt |
| Explique le rôle de transpilation automatique d’Astro | 0,5 pt |
a) Export sans import (0,5 pt)
Non, ce n’est pas un problème. Le compilateur ne signale aucune erreur. Exporter une fonction la rend disponible pour d’autres modules, mais ne les oblige pas à l’importer. C’est utile quand un module est une bibliothèque réutilisable : on exporte tout ce qui pourrait être utile, et chaque fichier importateur choisit ce dont il a besoin.
b) Fichier lune.ts (0,5 pt)
export const GRAVITE = 1.62;
export function vitesseChute(hauteur: number): number {
return Math.sqrt(2 * GRAVITE * hauteur);
}Résultats attendus :
- Terre : g = 9.81, vitesse 10m =
Math.sqrt(2 × 9.81 × 10)≈ 14.01 m/s - Lune : g = 1.62, vitesse 10m =
Math.sqrt(2 × 1.62 × 10)≈ 5.69 m/s
c) Conflit de nommage (0,5 pt)
TypeScript signale une erreur de compilation : Duplicate identifier 'GRAVITE'. Deux variables avec le même nom ne peuvent pas coexister dans le même scope. Solutions :
- Renommer avec
as:import { GRAVITE as G_LUNE } from "./lune.ts" - Importer tout le module :
import * as Lune from "./lune.ts"puisLune.GRAVITE
Grille de notation Q3
| Critère | Points |
|---|---|
| Explique qu’exporter sans importer n’est pas une erreur (bibliothèque réutilisable) | 0,5 pt |
| Fichier lune.ts correct et fonctionnel | 0,5 pt |
| Teste le conflit, identifie l’erreur duplicate identifier, propose une solution | 0,5 pt |
a) Décomposition du type (0,5 pt)
(a: number, b: number) => number: c’est un type de fonction — une fonction qui prend deuxnumberet retourne unnumber[]à la fin : signifie que c’est un tableau de tels éléments- Ensemble : un tableau de fonctions. Cela illustre le fait que les fonctions sont des « citoyens de première classe » en TypeScript/JavaScript : on peut les stocker dans des variables, des tableaux, les passer en paramètres.
b) Exécution pas à pas (0,5 pt)
appliquerTout(17, 5, operations) :
ops.map((op) => op(x, y))itère sur chaque fonction du tableau- Pour chaque fonction
op, on appelleop(17, 5):(a, b) => a + b→ 17 + 5 = 22(a, b) => a - b→ 17 - 5 = 12(a, b) => a * b→ 17 × 5 = 85(a, b) => a % b→ 17 % 5 = 2 (reste de la division)
- Résultat :
[22, 12, 85, 2]
c) Extension (0,5 pt)
// Ajout au tableau :
operations.push((a, b) => a > b ? a : b);
// Modification de appliquerTout :
const appliquerTout = (
x: number,
y: number,
ops: ((a: number, b: number) => number)[],
filtrer?: (n: number) => boolean
): number[] => {
const resultats = ops.map((op) => op(x, y));
if (filtrer) {
return resultats.filter(filtrer);
}
return resultats;
};
// Test avec filtre positifs :
const resultats = appliquerTout(17, 5, operations, (n) => n > 0);
console.log(resultats); // [22, 12, 85, 2, 17]
// Tous sont positifs ici. Avec appliquerTout(3, 5, operations, (n) => n > 0)
// on aurait [8, 15, 5, 3] (3-5 = -2 est filtré, 3%5=3 reste)Grille de notation Q4
| Critère | Points |
|---|---|
| Décompose correctement le type (type de fonction + tableau) | 0,5 pt |
| Exécute et explique pas à pas avec les bons résultats [22, 12, 85, 2] | 0,5 pt |
| Code fonctionnel : nouvelle opération + paramètre optionnel filtrer avec filter | 0,5 pt |
a) Expression chaînée (0,5 pt)
const densiteMoyenne = villes
.map((v) => ({ ...v, densite: v.population / v.superficie }))
.filter((v) => v.densite > 5000)
.reduce((acc, v, _i, arr) => acc + v.densite / arr.length, 0);Ou alternative sans le arr.length dans le reduce :
const villesDenses = villes
.map((v) => ({ nom: v.nom, population: v.population, superficie: v.superficie, densite: v.population / v.superficie }))
.filter((v) => v.densite > 5000);
const densiteMoyenne = villesDenses.reduce((acc, v) => acc + v.densite, 0) / villesDenses.length;Densités :
- Paris : 2161000/105.4 ≈ 20502.8 ✅ (> 5000)
- Marseille : 873076/240.6 ≈ 3628.5 ✗
- Lyon : 522250/47.9 ≈ 10903.9 ✅ (> 5000)
- Toulouse : 498003/118.3 ≈ 4209.7 ✗
- Nice : 342522/71.9 ≈ 4763.9 ✗
- Nantes : 318808/65.2 ≈ 4889.7 ✗
Villes retenues : Paris et Lyon. Densité moyenne : (20502.8 + 10903.9) / 2 ≈ 15703.4 hab/km²
b) Mutation dans map (0,5 pt)
Les deux console.log affichent la même valeur (2377100). Explication : map crée un nouveau tableau mais les objets à l’intérieur sont les mêmes références. En modifiant v.population, on modifie l’objet original partagé entre villes et villesModifiees.
Correction — créer un nouvel objet :
const villesModifiees = villes.map((v) => ({
nom: v.nom,
population: v.population * 1.1,
superficie: v.superficie,
}));Maintenant villes[0].population reste inchangé.
Note : on peut aussi utiliser { ...v, population: v.population * 1.1 } (opérateur de décomposition/spread), mais cette syntaxe n’a pas été vue en cours.
c) find avec includes (0,5 pt)
const ville = villes.find((v) => v.nom.toLowerCase().includes("o"));
console.log(ville); // { nom: "Toulouse", population: 498003, superficie: 118.3 }- Si aucune ville ne correspond,
findretourneundefined - Le type de retour est
{ nom: string; population: number; superficie: number } | undefined - TypeScript ajoute
| undefinedcar il n’y a aucune garantie qu’un élément correspondant existe dans le tableau. Cela oblige le développeur à gérer le cas « non trouvé » avant d’utiliser le résultat.
Grille de notation Q5
| Critère | Points |
|---|---|
| Expression chaînée correcte avec map+filter+reduce, bonnes villes identifiées | 0,5 pt |
| Identifie que les deux console.log affichent la même valeur + explique le passage par référence + propose correction | 0,5 pt |
| find correct avec includes + explique undefined et le type de retour | 0,5 pt |
a) Classe abstraite et visibilité (0,4 pt)
new SourceEnergie("Test", 100) provoque une erreur de compilation : une classe abstraite ne peut pas être instanciée directement.
console.log(eol.puissance) → Erreur : puissance est protected, accessible uniquement depuis la classe elle-même et ses classes filles, pas depuis l’extérieur.
console.log(eol.nombreTurbines) → Erreur : nombreTurbines est private, accessible uniquement depuis la classe Eolienne elle-même.
| Modificateur | Depuis la classe | Depuis les classes filles | Depuis l’extérieur |
|---|---|---|---|
public |
✅ | ✅ | ✅ |
protected |
✅ | ✅ | ❌ |
private |
✅ | ❌ | ❌ |
nom(protected) : accessible dansSourceEnergie,Eolienne,CentraleGaz, mais pas viaeol.nompuissance(protected) : idemnombreTurbines(private) : uniquement dansEoliennerendement(private) : uniquement dansCentraleGaz
b) Polymorphisme (0,5 pt)
C’est le polymorphisme. À l’exécution, JavaScript consulte la chaîne de prototypes de l’objet réel (pas du type déclaré) pour trouver la bonne implémentation de la méthode. Même si le tableau est typé ISourceEnergie[], chaque objet conserve son type réel.
Calculs :
- Parc Alpha (Eolienne, 3 MW × 20 turbines = 60 MW) :
- Production : 60 × 2200 = 132 000 MWh
- CO2 : 132 000 × 0.014 = 1 848 t CO2
- Centrale Beta (CentraleGaz, 500 MW, rendement 0.58) :
- Production : 500 × 5000 × 0.58 = 1 450 000 MWh
- CO2 : 1 450 000 × 0.49 = 710 500 t CO2
c) Classe PanneauSolaire (0,5 pt)
class PanneauSolaire extends SourceEnergie {
private surface: number;
constructor(nom: string, surface: number) {
super(nom, surface * 0.15 / 1000); // 0.15 MW par 1000 m²
this.surface = surface;
}
getProduction(): number {
return this.puissance * 1200; // 1200h/an
}
getEmissionCO2(): number {
return this.getProduction() * 0.043; // 0.043 t CO2/MWh
}
}
const ferme = new PanneauSolaire("Ferme Gamma", 5000);
// puissance = 5000 * 0.15 / 1000 = 0.75 MW
// production = 0.75 * 1200 = 900 MWh
// CO2 = 900 * 0.043 = 38.7 t CO2d) Interface vs classe abstraite pour le typage (0,4 pt)
Les deux fonctionneraient dans ce cas car Eolienne, CentraleGaz et PanneauSolaire héritent toutes de SourceEnergie.
L’interface est avantageuse si on veut ajouter une source d’énergie qui n’hérite pas de SourceEnergie. Par exemple, un ImportElectrique (achat d’électricité à l’étranger) qui n’a pas de puissance propre — il implémente directement ISourceEnergie sans passer par SourceEnergie. Avec SourceEnergie[], ce ne serait pas possible.
Une interface permet aussi l’implémentation multiple : une classe peut implémenter plusieurs interfaces, mais ne peut hériter que d’une seule classe.
e) Propriété statique (0,35 pt)
abstract class SourceEnergie implements ISourceEnergie {
static compteur: number = 0;
// ... reste identique ...
constructor(nom: string, puissance: number) {
this.nom = nom;
this.puissance = puissance;
SourceEnergie.compteur++;
}
}Après 3 créations : SourceEnergie.compteur vaut 3.
On y accède via SourceEnergie.compteur car une propriété static appartient à la classe, pas aux instances. eol.compteur ne causerait pas d’erreur d’exécution en JavaScript (il retournerait undefined ou la valeur si on y accède via le prototype), mais en TypeScript strict, le compilateur signale une erreur car compteur n’est pas une propriété d’instance. C’est parce que chaque instance n’a pas sa propre copie — il n’y a qu’une seule variable partagée.
f) Ajout de méthode à l’interface vs classe abstraite (0,35 pt)
Ajout dans l’interface : toutes les classes qui implémentent ISourceEnergie (directement ou via SourceEnergie) doivent fournir une implémentation de ratio(). Il faut modifier Eolienne, CentraleGaz, et PanneauSolaire → 3 modifications.
Ajout dans la classe abstraite (méthode concrète) :
ratio(): number {
return this.getProduction() / this.getEmissionCO2();
}Comme c’est une méthode concrète avec du code, toutes les classes filles en héritent automatiquement → 0 modification dans les classes filles. C’est l’avantage de la classe abstraite : centraliser le code commun.
Grille de notation Q6
| Critère | Points |
|---|---|
| a) Explique abstract non instanciable + protected/private avec tableau de visibilité | 0,4 pt |
| b) Identifie et explique le polymorphisme + calcule les valeurs correctes | 0,5 pt |
| c) Classe PanneauSolaire correcte avec héritage, super, méthodes implémentées | 0,5 pt |
| d) Explique l’avantage de l’interface (flexibilité, implémentation multiple) | 0,4 pt |
| e) Propriété static correcte + explication accès via classe vs instance | 0,35 pt |
| f) Compare interface (3 modifs) vs classe abstraite concrète (0 modif) | 0,35 pt |
Partie 2 — Exercice de codage
Barème : 10 points
import { Livre, livres, formatPrix } from "./catalogueLivres.ts";
const creerLigneTableau = (livre: Livre): string => {
const classe = livre.prix >= 20 ? "cher" : "abordable";
return `<tr class="${classe}">
<td>${livre.titre}</td>
<td>${livre.auteur}</td>
<td>${formatPrix(livre.prix)}</td>
<td>${livre.annee}</td>
<td>${livre.genre}</td>
</tr>`;
};
const lignesHTML: string[] = livres.map(creerLigneTableau);
const catalogueDiv = document.getElementById("catalogue") as HTMLDivElement;
catalogueDiv.innerHTML = `<table>
<thead><tr><th>Titre</th><th>Auteur</th><th>Prix</th><th>Année</th><th>Genre</th></tr></thead>
<tbody>${lignesHTML.join("")}</tbody>
</table>`;Barème détaillé :
- Import correct des 3 éléments (interface, tableau, fonction) : 0,50 pt
- Fonction fléchée
creerLigneTableauavec paramètre typé et retour HTML : 0,50 pt - Logique conditionnelle pour la classe CSS
cher/abordable: 0,50 pt - Utilisation de
map+.join("")+ affichage dans le DOM : 0,50 pt
const livresAbordables: Livre[] = livres.filter((livre: Livre) => livre.prix < 20);
const abordablesDiv = document.getElementById("livres-abordables") as HTMLDivElement;
abordablesDiv.innerHTML = `<ul>${livresAbordables.map(
(livre: Livre) => `<li>${livre.titre} - ${livre.auteur} (${formatPrix(livre.prix)})</li>`
).join("")}</ul>`;Résultat attendu : 6 livres (Les Misérables, Le Petit Prince, Germinal, L’Étranger, Fondation, 1984)
Barème détaillé :
- Utilisation correcte de
filteravec conditionprix < 20: 0,50 pt - Fonction fléchée correcte dans filter : 0,50 pt
- Affichage HTML sous forme de liste à puces : 0,50 pt
const titresMajuscules: string[] = livres.map((livre: Livre) => livre.titre.toUpperCase());
const majusculesDiv = document.getElementById("titres-majuscules") as HTMLDivElement;
majusculesDiv.innerHTML = `<ul>${titresMajuscules.map(
(titre: string) => `<li>${titre}</li>`
).join("")}</ul>`;Barème détaillé :
mapqui extrait et transforme les titres : 0,50 pt- Utilisation de
.toUpperCase(): 0,50 pt - Affichage correct : 0,50 pt
const prixTotal: number = livres.reduce((acc: number, livre: Livre) => acc + livre.prix, 0);
const prixMoyen: number = prixTotal / livres.length;
const statsDiv = document.getElementById("statistiques") as HTMLDivElement;
statsDiv.innerHTML = `
<p>Prix total du catalogue : ${formatPrix(prixTotal)}</p>
<p>Prix moyen : ${formatPrix(prixMoyen)}</p>
<p>Nombre de livres : ${livres.length}</p>
`;Valeurs attendues :
- Prix total : 113.20€
- Prix moyen : 14.15€
- Nombre de livres : 8
Barème détaillé :
- Utilisation correcte de
reduceavec accumulateur et valeur initiale0: 1,00 pt - Calcul du prix moyen et affichage des 3 statistiques : 0,50 pt
const premierSF: Livre | undefined = livres.find((livre: Livre) => livre.genre === "Science-Fiction");
const premierAvant1900: Livre | undefined = livres.find((livre: Livre) => livre.annee < 1900);
const rechercheDiv = document.getElementById("recherche") as HTMLDivElement;
rechercheDiv.innerHTML = `
<p><strong>Premier livre de Science-Fiction :</strong> ${
premierSF ? `${premierSF.titre} de ${premierSF.auteur} (${premierSF.annee})` : "Aucun livre trouvé"
}</p>
<p><strong>Premier livre publié avant 1900 :</strong> ${
premierAvant1900 ? `${premierAvant1900.titre} de ${premierAvant1900.auteur} (${premierAvant1900.annee})` : "Aucun livre trouvé"
}</p>
`;Valeurs attendues :
- Premier SF : Dune de Frank Herbert (1965)
- Avant 1900 : Les Misérables de Victor Hugo (1862)
Barème détaillé :
- Utilisation correcte de
find: 0,50 pt - Type
Livre | undefinedet gestion du casundefined: 0,50 pt - Affichage conditionnel avec opérateur ternaire : 0,50 pt
const romansAvecRemise: Livre[] = livres
.filter((livre: Livre) => livre.genre === "Roman")
.map((livre: Livre) => ({
titre: livre.titre,
auteur: livre.auteur,
prix: livre.prix * 0.9,
annee: livre.annee,
genre: livre.genre,
}));
const totalPanier: number = romansAvecRemise.reduce((acc: number, livre: Livre) => acc + livre.prix, 0);
const panierDiv = document.getElementById("panier") as HTMLDivElement;
panierDiv.innerHTML = "<h3>Romans avec 10% de remise :</h3>";
const romansOriginaux: Livre[] = livres.filter((livre: Livre) => livre.genre === "Roman");
romansAvecRemise.forEach((livre: Livre, index: number) => {
const p: HTMLParagraphElement = document.createElement("p");
const prixOriginal = romansOriginaux[index].prix;
p.textContent = `${livre.titre} — ${formatPrix(prixOriginal)} → ${formatPrix(livre.prix)}`;
panierDiv.appendChild(p);
});
const totalP: HTMLParagraphElement = document.createElement("p");
totalP.innerHTML = `<strong>Total du panier : ${formatPrix(totalPanier)}</strong>`;
panierDiv.appendChild(totalP);Valeurs attendues :
- Les Misérables : 12.50€ → 11.25€
- Germinal : 11.00€ → 9.90€
- L’Étranger : 7.50€ → 6.75€
- Total du panier : 27.90€
Barème détaillé :
- Chaînage
filter+map: 0,50 pt - Création de nouveaux objets (pas de mutation) : 0,50 pt
- Calcul du total avec
reduce: 0,50 pt - Utilisation de
forEach+createElement/appendChild: 0,50 pt