JavaScript Avancé
JS Avancé
& TypeScript
Prototype, classes, générateurs, modules, design patterns, performance — et TypeScript du typage de base aux génériques avancés.
01 — JS Avancé
Prototype & héritage prototypal
JavaScript n'a pas d'héritage classique — il utilise la chaîne de prototypes. Chaque objet a un lien vers un prototype dont il hérite des propriétés et méthodes.
Prototype — comment ça marche
// Tout objet a [[Prototype]] (accessible via __proto__)
const animal = {
parler() { return `${this.nom} fait du bruit`; },
};
const chien = Object.create(animal);
chien.nom = 'Rex';
chien.aboyer = function() { return 'Wouf!'; };
chien.parler(); // 'Rex fait du bruit'
// parler() introuvable sur chien → cherche dans animal ✓
// Chaîne de prototypes :
// chien → animal → Object.prototype → null
Object.getPrototypeOf(chien) === animal; // true
// hasOwnProperty — distingue propre vs hérité
chien.hasOwnProperty('nom'); // true (propre)
chien.hasOwnProperty('parler'); // false (hérité)
// instanceof — vérifie la chaîne
function Animal(nom) { this.nom = nom; }
const a = new Animal('Rex');
a instanceof Animal; // true
Héritage via prototype (avant classes)
// Fonction constructeur
function Animal(nom, son) {
this.nom = nom;
this.son = son;
}
Animal.prototype.parler = function() {
return `${this.nom} fait "${this.son}"`;
};
// Sous-classe (héritage prototypal)
function Chien(nom) {
Animal.call(this, nom, 'Wouf'); // appel parent
}
Chien.prototype = Object.create(Animal.prototype);
Chien.prototype.constructor = Chien;
Chien.prototype.chercher = function() {
return `${this.nom} rapporte la balle !`;
};
const rex = new Chien('Rex');
rex.parler(); // 'Rex fait "Wouf"'
rex.chercher(); // 'Rex rapporte la balle !'
// Today → toujours préférer les classes ES6
// qui font la même chose de façon lisible
02
Classes ES6+
Syntaxe complète — toutes les fonctionnalités
class Animal {
// Champ de classe (ES2022)
#nom; // privé (# obligatoire)
son = '...'; // public avec valeur par défaut
static count = 0; // propriété statique
constructor(nom, son) {
this.#nom = nom;
this.son = son;
Animal.count++;
}
// Getter / Setter
get nom() { return this.#nom; }
set nom(val) {
if (!val) throw new Error('Nom requis');
this.#nom = val;
}
parler() { return `${this.#nom}: ${this.son}`; }
// Méthode statique — appelée sur la classe
static compter() { return Animal.count; }
// Méthode privée
#valider() { return this.#nom.length > 0; }
}
class Chien extends Animal {
constructor(nom) {
super(nom, 'Wouf'); // OBLIGATOIRE avant this
}
// Override de méthode
parler() {
return `${super.parler()} 🐕`;
}
}
Mixins — composition vs héritage
// JS ne supporte pas l'héritage multiple.
// Les mixins permettent de composer des comportements.
const Serializable = (Base) => class extends Base {
toJSON() { return JSON.stringify(this); }
static fromJSON(json) {
return Object.assign(new this(), JSON.parse(json));
}
};
const Timestamped = (Base) => class extends Base {
constructor(...args) {
super(...args);
this.createdAt = new Date();
}
};
const Loggable = (Base) => class extends Base {
log() { console.log(JSON.stringify(this)); }
};
// Composer plusieurs comportements :
class User extends Loggable(Timestamped(Serializable(Animal))) {
constructor(nom, email) {
super(nom, '');
this.email = email;
}
}
const u = new User('Alice', 'alice@ex.com');
u.log(); // méthode de Loggable
u.toJSON(); // méthode de Serializable
u.createdAt; // propriété de Timestamped
03
Itérateurs & générateurs
Protocole itérable & itérateur personnalisé
// Un itérable implémente [Symbol.iterator]()
// qui retourne un itérateur { next() }
class Intervalle {
constructor(debut, fin, pas = 1) {
this.debut = debut;
this.fin = fin;
this.pas = pas;
}
[Symbol.iterator]() {
let courant = this.debut;
const fin = this.fin;
const pas = this.pas;
return {
next() {
if (courant <= fin) {
const value = courant;
courant += pas;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
for (const n of new Intervalle(1, 10, 2)) {
console.log(n); // 1, 3, 5, 7, 9
}
[...new Intervalle(1, 5)] // [1,2,3,4,5]
Générateurs — function*
// Un générateur est une fonction pausable.
// yield suspend et retourne une valeur.
function* fibonacci() {
let [a, b] = [0, 1];
while (true) { // séquence infinie !
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
fib.next().value; // 0
fib.next().value; // 1
fib.next().value; // 1
fib.next().value; // 2
// Prendre les 10 premiers
function* take(n, iterable) {
let count = 0;
for (const x of iterable) {
if (count++ >= n) return;
yield x;
}
}
[...take(10, fibonacci())]
// [0,1,1,2,3,5,8,13,21,34]
// yield* — déléguer à un autre itérable
function* concat(...arrays) {
for (const arr of arrays) yield* arr;
}
[...concat([1,2], [3,4], [5])]
// [1,2,3,4,5]
04
Proxy & Reflect
Proxy — intercepter les opérations sur un objet
// Proxy intercepte : get, set, has, deleteProperty,
// apply (appels de fonction), construct, etc.
const handler = {
// Intercepter la lecture d'une propriété
get(target, prop, receiver) {
if (prop in target) {
console.log(`Lecture de ${prop}`);
return Reflect.get(target, prop, receiver);
}
throw new ReferenceError(`Propriété "${prop}" inconnue`);
},
// Intercepter l'écriture
set(target, prop, value) {
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('age doit être un nombre');
}
return Reflect.set(target, prop, value);
},
};
const user = new Proxy({ nom: 'Alice', age: 25 }, handler);
user.nom; // log + 'Alice'
user.age = 'x'; // TypeError !
user.email; // ReferenceError !
Proxy — cas d'usage réels
// 1. Observable — détecter les changements
function observable(obj, onChange) {
return new Proxy(obj, {
set(target, prop, value) {
const old = target[prop];
const ok = Reflect.set(target, prop, value);
if (old !== value) onChange(prop, old, value);
return ok;
}
});
}
const state = observable(
{ count: 0 },
(prop, from, to) => console.log(`${prop}: ${from}→${to}`)
);
state.count++; // "count: 0→1"
// 2. Cache avec expiration
function withCache(target, ttl = 5000) {
const cache = new Map();
return new Proxy(target, {
get(t, prop) {
if (typeof t[prop] !== 'function') return t[prop];
return async (...args) => {
const key = `${prop}:${JSON.stringify(args)}`;
const hit = cache.get(key);
if (hit && Date.now() - hit.ts < ttl) return hit.val;
const val = await t[prop](...args);
cache.set(key, { val, ts: Date.now() });
return val;
};
}
});
}
05
Modules ES & CommonJS
ES Modules (ESM) — standard moderne
// math.js — exports nommés
export const PI = 3.14159;
export function somme(a, b) { return a + b; }
export class Vecteur { ... }
// api.js — export par défaut + nommés
export default class ApiClient { ... }
export { fetchUser, fetchPost };
// main.js — imports
import { PI, somme } from './math.js';
import { somme as add } from './math.js'; // alias
import * as Math from './math.js'; // namespace
import ApiClient from './api.js'; // défaut
import ApiClient, { fetchUser } from './api.js'; // les deux
// Import dynamique (lazy loading)
const module = await import('./lourd.js');
// Chargé seulement quand nécessaire !
// Import conditionnel
const { default: chart } = await import(
isMobile ? './chart-mobile.js' : './chart-desktop.js'
);
CommonJS (Node.js) vs ESM
// ── CommonJS (require/module.exports) ──────
// math.js
const PI = 3.14;
function somme(a, b) { return a + b; }
module.exports = { PI, somme };
// ou :
module.exports.somme = somme;
// main.js
const { PI, somme } = require('./math');
const fs = require('fs'); // module Node natif
// ── Différences clés ──────────────────────
// ESM : import statique → tree-shaking possible
// CJS : require dynamique → pas de tree-shaking
// ESM : top-level await supporté
// CJS : synchrone uniquement
// ESM : mode strict par défaut
// CJS : mode non-strict par défaut
// package.json pour ESM dans Node :
// { "type": "module" }
// ou extension .mjs pour les fichiers ESM
// Interop : importer CJS depuis ESM
import pkg from 'legacy-cjs-package'; // import défaut
const { fn } = pkg;
06
Design patterns JavaScript
Création Singleton
// Instance unique — ex: connexion DB, config
class ConfigService {
static #instance = null;
#config = {};
constructor() {
if (ConfigService.#instance) {
return ConfigService.#instance;
}
ConfigService.#instance = this;
}
set(key, val) { this.#config[key] = val; }
get(key) { return this.#config[key]; }
}
const c1 = new ConfigService();
const c2 = new ConfigService();
c1 === c2; // true — même instance
Comportement Observer / EventEmitter
class EventEmitter {
#handlers = new Map();
on(event, fn) {
if (!this.#handlers.has(event))
this.#handlers.set(event, new Set());
this.#handlers.get(event).add(fn);
return () => this.off(event, fn); // unsubscribe fn
}
off(event, fn) { this.#handlers.get(event)?.delete(fn); }
emit(event, ...args) {
this.#handlers.get(event)?.forEach(fn => fn(...args));
}
}
const bus = new EventEmitter();
const off = bus.on('login', user => console.log(user));
bus.emit('login', { nom: 'Alice' });
off(); // se désabonner
Structure Decorator
// Ajouter des comportements sans modifier la classe
function withLogging(fn) {
return function(...args) {
console.log(`Appel: ${fn.name}(${args})`);
const result = fn.apply(this, args);
console.log(`Résultat: ${result}`);
return result;
};
}
const somme = (a, b) => a + b;
const sommeLoggée = withLogging(somme);
sommeLoggée(2, 3); // log + 5
Création Factory & Builder
// Factory — créer des objets sans new
function créerBouton(type) {
const types = {
primary: { bg: 'blue', label: 'Confirmer' },
danger: { bg: 'red', label: 'Supprimer' },
};
return { ...types[type], onClick: () => {} };
}
// Builder — construction par étapes
class QueryBuilder {
#parts = { select: '*', where: [], limit: null };
select(...cols) { this.#parts.select = cols; return this; }
where(cond) { this.#parts.where.push(cond); return this; }
limit(n) { this.#parts.limit = n; return this; }
build() { return `SELECT ${this.#parts.select}...`; }
}
const query = new QueryBuilder()
.select('nom', 'email')
.where('actif = true')
.limit(10)
.build();
07
Performance & optimisation
WeakMap, WeakSet, WeakRef
// Map ordinaire — empêche le garbage collector
const cache = new Map();
cache.set(domElement, data); // domElement ne sera
// jamais libéré tant que cache existe !
// WeakMap — référence faible, GC peut libérer
const metadonnées = new WeakMap();
metadonnées.set(domElement, { clics: 0 });
// Si domElement est supprimé du DOM,
// il sera garbage-collecté automatiquement
// WeakMap — cache privé pour les classes
const _privé = new WeakMap();
class Sécurisé {
constructor() { _privé.set(this, { secret: 42 }); }
get val() { return _privé.get(this).secret; }
}
// WeakRef — référence faible à un objet
let ref = new WeakRef(grosObjet);
const obj = ref.deref();
if (obj) { /* objet toujours en vie */ }
Optimisations DOM & async
// DocumentFragment — batch d'insertions
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li); // pas de reflow ici
});
ul.appendChild(fragment); // 1 seul reflow !
// Throttle — max 1 appel par intervalle
function throttle(fn, delay) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= delay) {
last = now;
fn(...args);
}
};
}
window.addEventListener('scroll', throttle(fn, 100));
// requestAnimationFrame — animations fluides
function animer(progress) {
element.style.transform = `translateX(${progress}px)`;
if (progress < 100)
requestAnimationFrame(() => animer(progress + 1));
}
requestAnimationFrame(() => animer(0));
// Web Workers — calcul hors du thread UI
const worker = new Worker('worker.js');
worker.postMessage({ donnees: tableauLourd });
worker.onmessage = (e) => afficher(e.data.résultat);
08
Gestion d'erreurs avancée
Classes d'erreurs personnalisées
// Erreurs typées pour un meilleur catch
class AppError extends Error {
constructor(message, code, context = {}) {
super(message);
this.name = 'AppError';
this.code = code;
this.context = context;
Error.captureStackTrace(this, AppError); // Node
}
}
class NotFoundError extends AppError {
constructor(ressource, id) {
super(`${ressource} #${id} introuvable`, 'NOT_FOUND');
this.name = 'NotFoundError';
}
}
class ValidationError extends AppError {
constructor(champ, règle) {
super(`${champ} invalide : ${règle}`, 'VALIDATION');
this.name = 'ValidationError';
}
}
// Catch typé :
try {
await getUser(42);
} catch (err) {
if (err instanceof NotFoundError) afficher404();
else if (err instanceof ValidationError) afficherForm();
else throw err; // re-propager les inconnues
}
Result pattern — éviter les try/catch
// Inspiré de Rust — retourner {ok, err} plutôt que throw
function ok(value) { return { ok: true, value }; }
function err(error) { return { ok: false, error }; }
async function safeGetUser(id) {
try {
const user = await fetchUser(id);
return ok(user);
} catch (e) {
return err(e);
}
}
// Usage — pas de try/catch à chaque appel
const résultat = await safeGetUser(42);
if (!résultat.ok) {
afficherErreur(résultat.error);
return;
}
afficher(résultat.value);
// Erreurs non gérées — toujours écouter
window.addEventListener('unhandledrejection', (e) => {
console.error('Promise non gérée :', e.reason);
// logger.error(e.reason);
});
process.on('unhandledRejection', (reason) => {
// Node.js
console.error(reason);
process.exit(1);
});
09 — TypeScript
Pourquoi TypeScript ?
🟡 JavaScript
function calculerTVA(prix, taux) {
return prix * taux;
}
calculerTVA("100", 0.2); // "1001001001..."
calculerTVA(100); // NaN
// Bugs silencieux au runtime !
🟣 TypeScript
function calculerTVA(
prix: number,
taux: number
): number {
return prix * taux;
}
calculerTVA("100", 0.2);
// ⛔ Erreur à la compilation !
ℹ️
TypeScript est un superset de JavaScript — tout JS valide est du TS valide. Le compilateur tsc transpile TS → JS. Les types n'existent qu'à la compilation : ils disparaissent au runtime.
Installation & premier fichier
# Installation
npm install -D typescript
npm install -D ts-node # exécuter sans compiler
npm install -D @types/node
# Initialiser tsconfig.json
npx tsc --init
# Compiler
npx tsc # compile tout le projet
npx tsc --watch # recompile à chaque sauvegarde
npx ts-node app.ts # exécuter directement
// hello.ts
const message: string = 'Bonjour TypeScript';
console.log(message);
// Avantages TypeScript :
// ✓ Erreurs détectées à la compilation, pas au runtime
// ✓ Autocomplétion dans l'IDE (IntelliSense)
// ✓ Refactoring sûr (renommer, déplacer)
// ✓ Documentation vivante via les types
// ✓ Interopérabilité avec les libs JS (@types)
10
Types primitifs & annotations
Types de base
// Primitifs
let nom: string = 'Alice';
let age: number = 25;
let actif: boolean = true;
let inconnu: null = null;
let rien: undefined;
// Tableaux
const nombres: number[] = [1, 2, 3];
const noms: string[] = ['Alice', 'Bob'];
const mixed: Array<number | string> = [1, 'a'];
// Tuple — tableau de taille et types fixes
const point: [number, number] = [10, 20];
const entrée: [string, number] = ['age', 25];
const [clé, val] = entrée; // clé: string, val: number
// Types spéciaux
let a: any; // désactive le typage ← à éviter
let b: unknown; // sûr : forcer un check avant usage
let c: never; // valeur impossible (ex: switch exhaustif)
function loguer(msg: string): void { // pas de return
console.log(msg);
}
Union, intersection, literal types
// Union — plusieurs types possibles
let id: string | number;
id = 'abc'; // ✓
id = 42; // ✓
id = true; // ⛔ Type 'boolean' non assignable
function afficherID(id: string | number) {
if (typeof id === 'string') {
console.log(id.toUpperCase()); // id est string ici
} else {
console.log(id.toFixed(2)); // id est number ici
}
}
// Literal types — valeurs exactes
type Direction = 'nord' | 'sud' | 'est' | 'ouest';
type Dé = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = true | false; // = boolean
function aller(dir: Direction) { ... }
aller('nord'); // ✓
aller('gauche'); // ⛔ Erreur !
// as const — inférer les literal types
const DIRECTIONS = ['nord', 'sud'] as const;
// type: readonly ["nord", "sud"]
type Dir = typeof DIRECTIONS[number]; // "nord" | "sud"
11
Interfaces & type aliases
Interface — contrat de forme
interface User {
readonly id: number; // lecture seule
nom: string;
email: string;
age?: number; // optionnel
adresse?: Adresse;
saluer(): string; // méthode
}
interface Adresse {
rue: string;
ville: string;
cp?: string;
}
// Extension d'interface
interface Admin extends User {
role: 'admin' | 'superadmin';
permissions: string[];
}
// Fusion de déclarations (declaration merging)
interface Window {
monPlugin: { init(): void }; // ajouter à Window global
}
// Interface avec index signature
interface Dictionnaire<T> {
[clé: string]: T;
}
const scores: Dictionnaire<number> = {
Alice: 95, Bob: 87
};
type alias — plus flexible
// type alias — peut définir n'importe quel type
type ID = string | number;
type Callback = (err: Error | null, data: any) => void;
type Point = { x: number; y: number };
// Intersection — combiner des types
type UserAdmin = User & { role: string };
type WithTimestamp<T> = T & {
createdAt: Date;
updatedAt: Date;
};
// interface vs type — quand utiliser quoi ?
// interface :
// ✓ Objets et classes (forme attendue)
// ✓ Déclaration merging nécessaire
// ✓ Héritage (extends)
//
// type :
// ✓ Unions, intersections, tuples
// ✓ Types complexes (mapped, conditional)
// ✓ Alias d'autres types
//
// Pour les objets simples : interface ou type,
// les deux fonctionnent — choisir et rester cohérent
12
Fonctions typées
Signatures et overloads
// Paramètres et retour typés
function add(a: number, b: number): number {
return a + b;
}
// Paramètre optionnel et valeur par défaut
function saluer(nom: string, titre?: string): string {
return titre ? `${titre} ${nom}` : nom;
}
// Rest parameter
function somme(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
// Type d'une fonction (signature)
type Comparateur<T> = (a: T, b: T) => number;
type Handler = (event: MouseEvent) => void;
// Function overloads — plusieurs signatures
function parse(val: string): number;
function parse(val: number): string;
function parse(val: string | number) {
return typeof val === 'string'
? Number(val) : String(val);
}
parse('42'); // → number
parse(42); // → string
Assertions et non-null
// Non-null assertion ! — affirmer que ≠ null
const canvas = document
.querySelector<HTMLCanvasElement>('#canvas')!;
// Le ! dit : "je garantis que ce n'est pas null"
// Type assertion as
const input = document.getElementById('username')
as HTMLInputElement;
input.value; // OK — HTMLElement n'a pas .value
// satisfies (TS 4.9) — vérifier sans changer le type
const config = {
port: 3000,
host: 'localhost',
} satisfies { port: number; host: string };
config.port // inféré comme number (pas number|string)
config.port.toFixed(); // OK ✓
// Narrowing avec type predicates
function estString(val: unknown): val is string {
return typeof val === 'string';
}
function traiter(val: unknown) {
if (estString(val)) {
val.toUpperCase(); // val: string ici ✓
}
}
13
Classes TypeScript & modificateurs
Modificateurs d'accès & propriétés
class BankAccount {
// Raccourci : déclarer et initialiser dans constructor
constructor(
public readonly owner: string, // public + readonly
private balance: number = 0, // privé TS
protected bank: string = 'BNP', // accessible sous-classes
) {}
get solde() { return this.balance; }
déposer(montant: number): this { // retourne this (fluent)
if (montant <= 0) throw new Error('Montant invalide');
this.balance += montant;
return this;
}
retirer(montant: number): this {
if (montant > this.balance) throw new Error('Solde insuffisant');
this.balance -= montant;
return this;
}
}
const compte = new BankAccount('Alice', 100);
compte.déposer(50).retirer(30); // fluent API
compte.balance; // ⛔ private → erreur TS
Implémentation d'interface & classes abstraites
// Interface — contrat que la classe doit respecter
interface Repo<T, ID> {
findById(id: ID): Promise<T | null>;
findAll(): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: ID): Promise<void>;
}
class UserRepo implements Repo<User, number> {
async findById(id: number) { ... }
async findAll() { ... }
async save(user: User) { ... }
async delete(id: number) { ... }
}
// Classe abstraite — ne peut pas être instanciée
abstract class Shape {
abstract area(): number; // doit être implémenté
abstract perimeter(): number;
describe(): string { // méthode concrète
return `Aire=${this.area().toFixed(2)}`;
}
}
class Circle extends Shape {
constructor(private r: number) { super(); }
area() { return Math.PI * this.r ** 2; }
perimeter() { return 2 * Math.PI * this.r; }
}
14 — TS Avancé
Génériques (Generics)
Les génériques permettent d'écrire du code réutilisable et typé — une fonction ou classe qui fonctionne avec n'importe quel type, tout en conservant la sécurité du typage.
Fonctions et classes génériques
// Sans générique — perd le type
function identité(x: any): any { return x; }
const r = identité(42); // type: any ← on perd number
// Avec générique <T> — conserve le type
function identité<T>(x: T): T { return x; }
const r = identité(42); // type: number ✓
const s = identité('bonjour'); // type: string ✓
// Fonctions utilitaires génériques
function premier<T>(arr: T[]): T | undefined {
return arr[0];
}
function zip<A, B>(a: A[], b: B[]): [A, B][] {
return a.map((el, i) => [el, b[i]]);
}
zip([1,2,3], ['a','b','c']);
// [[1,'a'], [2,'b'], [3,'c']]
// Classe générique
class Stack<T> {
private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items.at(-1); }
get size(): number { return this.items.length; }
}
const stack = new Stack<number>();
stack.push(1); stack.push(2);
stack.pop(); // number ✓
Contraintes — extends
// Contraindre T à avoir certaines propriétés
function getLength<T extends { length: number }>(x: T): number {
return x.length;
}
getLength('bonjour'); // ✓ string a .length
getLength([1,2,3]); // ✓ array a .length
getLength(42); // ⛔ number n'a pas .length
// keyof — contraindre à une clé existante
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { nom: 'Alice', age: 25 };
getProperty(user, 'nom'); // string ✓
getProperty(user, 'age'); // number ✓
getProperty(user, 'xyz'); // ⛔ 'xyz' n'est pas une clé de user
// Valeur par défaut de générique (TS 2.3+)
interface Response<T = unknown> {
data: T;
status: number;
message: string;
}
type UserResponse = Response<User>;
type AnyResponse = Response; // T = unknown par défaut
15
Types utilitaires intégrés
Transformation d'objets
interface User {
id: number;
nom: string;
email: string;
motDePasse: string;
}
// Partial<T> — toutes les propriétés optionnelles
type UserUpdate = Partial<User>;
// { id?: number, nom?: string, ... }
// Required<T> — toutes obligatoires
type UserComplete = Required<User>;
// Readonly<T> — toutes en lecture seule
type UserFrozen = Readonly<User>;
// Pick<T, K> — garder seulement K
type UserPublic = Pick<User, 'id' | 'nom' | 'email'>;
// Omit<T, K> — enlever K
type UserSansMotDePasse = Omit<User, 'motDePasse'>;
// Record<K, V> — objet avec clés K et valeurs V
type Scores = Record<string, number>;
type PermissionsMap = Record<
'read' | 'write' | 'delete',
boolean
>;
Types conditionnels & autres utilitaires
// Exclude<T, U> — supprimer U de l'union T
type Nombres = string | number | boolean;
type SansString = Exclude<Nombres, string>;
// number | boolean
// Extract<T, U> — garder ce qui est dans les deux
type OnlyNumber = Extract<Nombres, number>;
// number
// NonNullable<T> — enlever null et undefined
type StringNonNulle = NonNullable<string | null | undefined>;
// string
// ReturnType<T> — type de retour d'une fonction
function getUser() { return { nom: 'Alice', age: 25 }; }
type UserResult = ReturnType<typeof getUser>;
// { nom: string; age: number }
// Parameters<T> — types des paramètres
type Params = Parameters<typeof getUser>;
// []
// Awaited<T> — type résolu d'une Promise (TS 4.5)
type Data = Awaited<Promise<User[]>>;
// User[]
// InstanceType<T> — type d'instance d'une classe
type UserInstance = InstanceType<typeof User>;
16
Types avancés — mapped, conditional, template
Mapped types — transformer chaque propriété
// Mapped type — itérer sur les clés d'un type
type Optional<T> = {
[K in keyof T]?: T[K]; // = Partial<T>
};
type ReadOnly<T> = {
readonly [K in keyof T]: T[K];
};
// Remapper les clés avec as
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// User → { getNom: () => string, getAge: () => number }
type EventMap<T> = {
[K in keyof T as `on${Capitalize<string & K>}Changed`]:
(val: T[K]) => void;
};
// Template literal types
type EventName = `on${string}`;
type CSSProperty = `--${string}`;
type Route = `/${string}`;
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = `${Method} /${string}`;
// "GET /users", "POST /auth", etc.
Conditional types — types qui dépendent d'autres
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
IsString<string>; // true
IsString<number>; // false
// infer — extraire un type conditionnel
type UnpackArray<T> = T extends (infer U)[] ? U : T;
UnpackArray<number[]>; // number
UnpackArray<string>; // string
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
UnpackPromise<Promise<User>>; // User
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
ToArray<string | number>;
// string[] | number[] (distribué sur l'union)
// DeepReadonly — récursif
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
17
Narrowing & type guards
Techniques de narrowing
type Animal = { nom: string; son: string };
type Chien = Animal & { race: string };
type Chat = Animal & { vies: number };
// typeof narrowing
function doublé(x: string | number) {
if (typeof x === 'string') return x.repeat(2);
return x * 2;
}
// instanceof narrowing
function traiterErreur(e: unknown) {
if (e instanceof ValidationError) { ... }
else if (e instanceof NetworkError) { ... }
else throw e;
}
// in narrowing — vérifier une propriété
function parler(a: Chien | Chat) {
if ('race' in a) {
console.log(`Chien race ${a.race}`); // a: Chien
} else {
console.log(`Chat avec ${a.vies} vies`); // a: Chat
}
}
Discriminated unions & exhaustive check
// Discriminated union — champ discriminant commun
type Loading = { status: 'loading' };
type Success = { status: 'success'; data: User[] };
type Error = { status: 'error'; message: string };
type State = Loading | Success | Error;
function render(state: State) {
switch (state.status) {
case 'loading':
return 'Chargement...';
case 'success':
return state.data; // state: Success ✓
case 'error':
return state.message; // state: Error ✓
default:
// Exhaustive check — jamais atteint
const _exhaustive: never = state;
return _exhaustive;
// Si on ajoute un type à State sans gérer
// ce case → erreur TS ici ✓
}
}
// User-defined type guard
function isUser(val: unknown): val is User {
return (
typeof val === 'object' && val !== null &&
'id' in val && 'nom' in val
);
}
function traiter(val: unknown) {
if (isUser(val)) val.nom; // val: User ✓
}
18
tsconfig.json & outillage
tsconfig.json — options essentielles
{
"compilerOptions": {
// Cible d'émission JavaScript
"target": "ES2022", // version JS générée
"module": "NodeNext", // système de modules
"lib": ["ES2022", "DOM"], // APIs dispo
// Chemins
"rootDir": "./src",
"outDir": "./dist",
// Rigueur du typage
"strict": true, // active TOUT
"noImplicitAny": true, // interdit any implicite
"strictNullChecks": true, // null ≠ string
"strictFunctionTypes": true,
"noUncheckedIndexedAccess": true, // arr[0]: T|undefined
// Qualité de code
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
// Décorateurs (Angular, NestJS)
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// Source maps pour le debug
"sourceMap": true,
"declaration": true // génère .d.ts
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
Erreurs TS communes & solutions
// 1. Object is possibly 'null' or 'undefined'
const el = document.getElementById('app'); // HTMLElement | null
el.innerHTML = 'hello'; // ⛔
// Solutions :
if (el) el.innerHTML = 'hello'; // guard ✅
el?.innerHTML = 'hello'; // optional chaining ✅
el!.innerHTML = 'hello'; // assertion (si certain) ✅
// 2. Property does not exist on type
const user: User = getUser();
user.foo; // ⛔ 'foo' n'existe pas sur User
// → Ajouter la propriété à l'interface, ou :
(user as any).foo; // dernier recours
// 3. Argument of type X is not assignable to Y
function fn(x: number) {}
fn('42'); // ⛔
fn(Number('42')); // ✅
// 4. Type 'X' has no call signatures
const val = config[key]; // type: any → problème ?
// Typer config avec Record<> ou interface
// 5. No overload matches this call
// → Vérifier que les arguments correspondent à
// une des signatures de surcharge
19 — Référence
Cheat sheet JS Avancé + TypeScript
JS Avancé — rappels
| Prototype chain | Cherche propriété → parent → Object |
| Classe privée | #champ (natif ES2022) |
| Mixin | (Base) => class extends Base |
| function* | Générateur — yield suspend |
| Proxy | Intercepter get/set/apply |
| WeakMap | Référence faible → GC peut libérer |
TypeScript — types clés
| any | Désactive le typage — à éviter |
| unknown | Sûr — vérifier avant usage |
| never | Valeur impossible — switch exhaustif |
| T | U | Union — l'un ou l'autre |
| T & U | Intersection — les deux |
| keyof T | Union des clés de T |
Génériques
| fn<T>(x: T): T | Inférer et conserver le type |
| T extends U | T doit avoir les props de U |
| keyof T | Limiter aux clés existantes |
| infer U | Extraire un type conditionnel |
| T = Default | Valeur par défaut du générique |
Types utilitaires
| Partial<T> | Toutes optionnelles |
| Required<T> | Toutes obligatoires |
| Pick<T, K> | Garder K seulement |
| Omit<T, K> | Enlever K |
| Record<K, V> | Objet clés K → valeurs V |
| ReturnType<F> | Type de retour de F |