Modern Frontend Engineering7.1 Les 5 Règles de
Chapitre 5
Chapitre 05 — JavaScript : Le Langage du Web
Chapitre 05 — JavaScript : Le Langage du Web
Chapitre 05 — JavaScript : Cours Complet
1. Histoire d'ECMAScript
1.1 De Mocha à ECMAScript
JavaScript est créé en 10 jours par Brendan Eich chez Netscape en mai 1995. Nommé Mocha, puis LiveScript, puis JavaScript (marketing). En 1996, Netscape soumet le langage à l'ECMA pour standardisation.
Diagramme en cours de génération...
1.2 Le Cycle de Release Annuel
Depuis ES2015, une nouvelle version d'ECMAScript est publiée chaque année en juin. Chaque proposition passe par 4 stages avant d'être incluse :
- Stage 1 : Proposal (idée)
- Stage 2 : Draft (première spécification)
- Stage 3 : Candidate (implémentations navigateurs)
- Stage 4 : Finished (inclus dans la spec)
2. Types : Primitifs vs Objets
2.1 Les 7 Types Primitifs
// Primitifs (immutables, passés par valeur)
undefined
null
boolean
number
string
symbol // ES2015
bigint // ES2020
// Objets (mutables, passés par référence)
Object
Array
Function
Date
RegExp
Map, Set, WeakMap, WeakSet
2.2 typeof et instanceof
typeof undefined // "undefined"
typeof null // "object" (bug historique depuis 1995)
typeof true // "boolean"
typeof 42 // "number"
typeof "hello" // "string"
typeof Symbol() // "symbol"
typeof 42n // "bigint"
typeof {} // "object"
typeof [] // "object"
typeof function(){} // "function"
// instanceof pour les objets
[] instanceof Array // true
{} instanceof Object // true
(function(){}) instanceof Function // true
// ⚠️ Piège : typeof null === "object"
// Solution :
function isNull(v) { return v === null; }
// ⚠️ typeof NaN === "number"
Number.isNaN(NaN) // true (Array.isArray-like)
2.3 Conversion de Type (Coercion)
// Coercion implicite (source de bugs)
1 + "2" // "12"
"5" - 3 // 2 (string → number automatique)
!"hello" // false
!!"hello" // true
[] + [] // "" (string vide)
{} + [] // 0 ({} est un bloc, +[] = 0)
// Coercion explicite (toujours préférer)
String(42) // "42"
Number("42") // 42
Boolean("hello") // true
parseInt("42px", 10) // 42
+"42" // 42 (unary operator)
3. Closures
3.1 Mécanisme Interne
Une closure est la combinaison d'une fonction et de son environnement lexical (scope) au moment de sa création.
function creerCompteur() {
let count = 0; // variable dans le scope de creerCompteur
return function() {
count++; // closure : capture count
return count;
};
}
const compteurA = creerCompteur();
const compteurB = creerCompteur();
compteurA(); // 1
compteurA(); // 2
compteurB(); // 1 (indépendant)
Diagramme en cours de génération...
3.2 Cas d'Usage
// 1. Factory functions
function createLogger(prefix) {
return function(message) {
console.log(`[${prefix}] ${message}`);
};
}
const info = createLogger('INFO');
const error = createLogger('ERROR');
info('Application démarrée'); // [INFO] Application démarrée
// 2. Module pattern (IIFE)
const counter = (function() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
})();
// 3. Event listeners
function attachClickHandler(id) {
document.getElementById(id).addEventListener('click', () => {
console.log(`Click on ${id}`); // closure sur id
});
}
3.3 Pièges des Closures
// ❌ Problème classique dans les boucles (var)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3
}
// ✅ Solution 1 : let (block scope)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}
// ✅ Solution 2 : IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
// ✅ Solution 3 : closure factory
for (var i = 0; i < 3; i++) {
setTimeout(createLog(i), 100);
}
function createLog(n) {
return () => console.log(n);
}
3.4 Memory Leaks avec Closures
function createLeak() {
const heavyData = new Array(1000000).fill('data');
return function() {
console.log('hello');
// heavyData est capturé mais jamais utilisé !
// Le GC ne peut pas libérer heavyData tant que cette closure existe
};
}
4. Scope : Global, Function, Block
// Global scope
const globalVar = 'accessible partout';
function scopeDemo() {
// Function scope
var functionVar = 'accessible dans la fonction';
if (true) {
// Block scope (let, const)
let blockLet = 'block scope';
const blockConst = 'block scope';
var functionVar2 = 'pas de block scope pour var';
}
console.log(functionVar2); // OK
// console.log(blockLet); // ReferenceError
}
// Lexical scoping : la portée est déterminée par la position dans le code source
const x = 'global';
function outer() {
const x = 'outer';
function inner() {
const x = 'inner';
console.log(x); // 'inner'
}
inner();
}
outer();
5. Hoisting
// var : hoistée avec valeur undefined
console.log(a); // undefined (pas d'erreur !)
var a = 5;
// let/const : hoistées mais pas initialisées (Temporal Dead Zone)
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 5;
// Function declarations : hoistées complètement
hello(); // "Hello!"
function hello() {
console.log("Hello!");
}
// Function expressions : hoistées comme var
sayHi(); // TypeError: sayHi is not a function
var sayHi = function() {
console.log("Hi!");
};
// TDZ illustrée
{
// Début de la TDZ pour 'x'
const func = () => console.log(x);
let x = 3; // Fin de la TDZ
func(); // 3 (car func est appelé APRÈS la déclaration)
}
Diagramme en cours de génération...
6. Event Loop
6.1 Architecture
Diagramme en cours de génération...
6.2 Ordre d'Exécution
console.log('1: sync');
setTimeout(() => console.log('2: setTimeout'), 0);
Promise.resolve().then(() => console.log('3: Promise microtask'));
queueMicrotask(() => console.log('4: queueMicrotask'));
requestAnimationFrame(() => console.log('5: rAF'));
console.log('6: sync');
// Output :
// 1: sync
// 6: sync
// 3: Promise microtask
// 4: queueMicrotask
// 2: setTimeout (après toutes les microtasks)
// 5: rAF (avant le prochain paint)
6.3 Règles Importantes
- Le Call Stack doit être vide avant de traiter les queues
- Les microtasks sont vidées complètement avant chaque task
- Une microtask qui en ajoute une autre retarde la prochaine task
requestAnimationFrames'exécute avant le paint, après les microtasks
// ⚠️ Microtask infinite loop (bloque le navigateur)
function loop() {
Promise.resolve().then(loop);
}
loop(); // ❌ Les tasks (click, setTimeout) ne s'exécuteront jamais
7. this : Contexte d'Exécution
7.1 Les 5 Règles de this
// 1. 🔵 Default binding (strict mode)
function showThis() {
'use strict';
console.log(this); // undefined
}
showThis();
// 2. 🔵 Default binding (non-strict)
function showThis() {
console.log(this); // globalThis (window)
}
// 3. 🟢 Implicit binding (objet appelant)
const obj = {
name: 'obj',
show() { console.log(this.name); }
};
obj.show(); // 'obj'
const detached = obj.show;
detached(); // undefined ou window (perte du contexte)
// 4. 🟡 Explicit binding (call, apply, bind)
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
const user = { name: 'Alice' };
greet.call(user, 'Hello'); // Hello, Alice
greet.apply(user, ['Hi']); // Hi, Alice
const bound = greet.bind(user);
bound('Hey'); // Hey, Alice
// 5. 🔴 new binding (constructeur)
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
// 6. 🟣 Arrow functions (lexical this, pas de binding)
const arrow = {
name: 'arrow',
show: () => console.log(this.name), // this = window/undefined
showRegular() {
const inner = () => console.log(this.name); // this = objet
inner();
}
};
7.2 Perte de Contexte et Solutions
class Button {
constructor(text) {
this.text = text;
// Solution 1 : bind dans constructor
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this.text);
}
// Solution 2 : arrow function dans class
handleClickArrow = () => {
console.log(this.text);
};
}
// Solution 3 : closure dans event listener
button.addEventListener('click', () => instance.handleClick());
8. Prototypes vs Classes
8.1 Héritage Prototypal
// Chaque objet a un prototype (sauf Object.create(null))
const parent = { name: 'parent' };
const child = Object.create(parent);
child.age = 10;
console.log(child.name); // 'parent' (via prototype)
console.log(child.age); // 10 (propre)
// La chaîne de prototypes
console.log(child.__proto__ === parent); // true
console.log(parent.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
Diagramme en cours de génération...
8.2 Constructeurs (Pré-ES6)
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} fait du bruit`;
};
function Dog(name, breed) {
Animal.call(this, name); // Appel du constructeur parent
this.breed = breed;
}
// Héritage du prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
return `${this.name} (${this.breed}) aboie`;
};
const rex = new Dog('Rex', 'Berger');
rex.speak(); // "Rex (Berger) aboie"
8.3 Classes ES6 (Syntax Sugar)
class Animal {
#privateField = 'privé'; // Private fields (ES2022)
constructor(name) {
this.name = name;
}
// Méthode de prototype
speak() {
return `${this.name} fait du bruit`;
}
// Méthode statique
static isAnimal(obj) {
return obj instanceof Animal;
}
// Getter
get description() {
return `Animal: ${this.name}`;
}
// Méthode privée
#privateMethod() {
return `Secret: ${this.#privateField}`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Obligatoire avant d'utiliser this
this.breed = breed;
}
speak() {
return `${super.speak()} → aboie`;
}
}
9. Async : Callbacks → Promises → async/await
9.1 L'Évolution
Diagramme en cours de génération...
9.2 Promises en Détail
// Création
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) resolve('Données');
else reject(new Error('Échec'));
}, 1000);
});
// Consommation
promise
.then(data => {
console.log(data);
return process(data);
})
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Terminé'));
// Combinators
Promise.all([fetch('/a'), fetch('/b')]) // Toutes réussissent
Promise.allSettled([fetch('/a'), fetch('/b')]) // Toutes, échouées ou non
Promise.race([fetch('/a'), fetch('/b')]) // La première terminée
Promise.any([fetch('/a'), fetch('/b')]) // La première réussie (ES2021)
Promise.withResolvers() // ES2024
9.3 async/await
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
return data;
} catch (error) {
console.error('Erreur fetch:', error);
throw error; // Re-lancer pour le caller
}
}
// Top-level await (ES2022) : dans les modules
const config = await fetch('/config.json').then(r => r.json());
// Parallel execution
async function parallel() {
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
return { users, posts };
}
// Sequential vs parallel
async function sequence() {
const a = await fetch('/api/a'); // 1s
const b = await fetch('/api/b'); // 1s (attend a)
// Total : 2s
}
async function parallel2() {
const [a, b] = await Promise.all([
fetch('/api/a'), // 1s
fetch('/api/b') // 1s (en parallèle)
]);
// Total : 1s
}
10. Modules : ES Modules, CommonJS, Dynamic Imports
// math.js - ES Module (ES2015)
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export default class Calculator {
// ...
}
// app.js - Import
import Calculator, { PI, add } from './math.js';
// Dynamic import (ES2020)
const module = await import('./math.js');
console.log(module.PI);
// CommonJS (Node.js legacy)
// math.cjs
const PI = 3.14159;
module.exports = { PI };
// app.cjs
const { PI } = require('./math.cjs');
| Aspect | ES Modules | CommonJS |
|---|---|---|
| Syntaxe | import/export | require()/module.exports |
| Statique | ✅ Analyse statique | ❌ Dynamique |
| Tree-shaking | ✅ Possible | ❌ Impossible |
| Cycle | ✅ Géré | ❌ Problématique |
| Top-level await | ✅ Oui | ❌ Non |
| Navigateur | ✅ Natif | ❌ Bundler nécessaire |
11. Gestion Mémoire
11.1 V8 Garbage Collector
Diagramme en cours de génération...
- Young Generation : objets récents, collectés fréquemment (Scavenger)
- Old Generation : objets qui survivent, collectés moins souvent (Mark-Sweep-Compact)
11.2 Memory Leaks Courants
// 1. Variables globales involontaires
function leak() {
leaked = 'devient global'; // pas de var/let/const
}
// 2. Closures qui capturent trop
function createLeak() {
const bigData = new Array(1000000);
return () => console.log('small');
}
// 3. Timers non nettoyés
const interval = setInterval(() => {
const heavy = new Array(100000);
console.log(heavy.length);
}, 100);
// clearInterval(interval) manquant
// 4. Event listeners non détachés
class Component {
constructor() {
window.addEventListener('resize', this.handleResize);
// ❌ Jamais de removeEventListener
}
}
// 5. Références DOM orphelines
const elements = [];
const div = document.createElement('div');
elements.push(div);
document.body.removeChild(div);
// elements[0] garde une référence → pas de GC
// ✅ Solutions
// - Utiliser WeakRef ou FinalizationRegistry si nécessaire
// - Toujours nettoyer les timers et listeners
// - Éviter les globaux implicites
// - Utiliser les outils Chrome Memory/Performance
12. Proxy, Reflect, Symbol, Generators
12.1 Proxy et Reflect
const handler = {
get(target, prop, receiver) {
if (prop in target) {
return Reflect.get(target, prop, receiver);
}
return `Propriété "${String(prop)}" inexistante`;
},
set(target, prop, value) {
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('age doit être un nombre');
}
return Reflect.set(target, prop, value);
},
deleteProperty(target, prop) {
console.log(`Suppression de ${String(prop)}`);
return Reflect.deleteProperty(target, prop);
}
};
const person = new Proxy({ name: 'Alice' }, handler);
console.log(person.name); // 'Alice'
console.log(person.unknown); // 'Propriété "unknown" inexistante'
person.age = 30; // OK
// person.age = 'trente'; // TypeError
12.2 Symbol
const UNIQUE = Symbol('description');
const obj = {
[UNIQUE]: 'valeur privée',
regular: 'publique'
};
Object.keys(obj); // ['regular']
Object.getOwnPropertySymbols(obj); // [Symbol(description)]
// Symboles well-known
const iterable = {
[Symbol.iterator]: function* () {
yield 1; yield 2; yield 3;
}
};
// Métaprogrammation
class MyClass {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
[] instanceof MyClass; // true
12.3 Generators et Iterators
// Generator function
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
fib.next(); // { value: 0, done: false }
fib.next(); // { value: 1, done: false }
fib.next(); // { value: 1, done: false }
// Iterator personnalisé
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
for (const n of new Range(1, 5)) {
console.log(n); // 1, 2, 3, 4, 5
}
13. Design Patterns en JavaScript
13.1 Module Pattern
// Module Pattern (IIFE)
const UserModule = (function() {
const users = new Map();
let idCounter = 0;
function validateEmail(email) {
return email.includes('@');
}
return {
addUser(name, email) {
if (!validateEmail(email)) throw new Error('Email invalide');
const id = ++idCounter;
users.set(id, { id, name, email });
return id;
},
getUser(id) {
return users.get(id);
},
getAllUsers() {
return Array.from(users.values());
}
};
})();
13.2 Observer Pattern
class EventEmitter {
constructor() {
this._events = new Map();
}
on(event, listener) {
if (!this._events.has(event)) {
this._events.set(event, []);
}
this._events.get(event).push(listener);
return () => this.off(event, listener); // unsubscribe
}
off(event, listener) {
const listeners = this._events.get(event);
if (listeners) {
this._events.set(event, listeners.filter(l => l !== listener));
}
}
emit(event, ...args) {
const listeners = this._events.get(event);
if (listeners) {
listeners.forEach(listener => listener(...args));
}
}
}
13.3 Singleton Pattern
class Database {
static #instance;
constructor() {
if (Database.#instance) {
return Database.#instance;
}
this.connection = null;
Database.#instance = this;
}
static getInstance() {
if (!Database.#instance) {
Database.#instance = new Database();
}
return Database.#instance;
}
}
13.4 Factory Pattern
class ButtonFactory {
createButton(type, options) {
switch (type) {
case 'primary':
return new PrimaryButton(options);
case 'secondary':
return new SecondaryButton(options);
case 'danger':
return new DangerButton(options);
default:
throw new Error(`Type inconnu: ${type}`);
}
}
}
14. Erreurs des Juniors
// ❌ Hoisting trap avec var
var x = 5;
(function() {
console.log(x); // undefined (hoisted var, pas la globale)
var x = 10;
})();
// ✅ Avec let (ReferenceError claire)
(function() {
console.log(y); // ReferenceError (TDZ)
let y = 10;
})();
// ❌ Closure leak dans boucle
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100); // 5, 5, 5, 5, 5
}
// ✅ let dans la boucle
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2, 3, 4
}
// ❌ Async mistake : pas de await dans forEach
async function wrong(items) {
items.forEach(async item => {
await process(item); // ⚠️ Pas attendu !
});
console.log('Fini'); // S'exécute avant !
}
// ✅ Solution : for...of
async function right(items) {
for (const item of items) {
await process(item);
}
console.log('Fini'); // Après tout
}
// ❌ Mutation non intentionnelle
const arr = [1, 2, 3];
const copy = arr;
copy.push(4);
console.log(arr); // [1, 2, 3, 4] ⚠️
// ✅ Copie réelle
const copy2 = [...arr];
const copy3 = structuredClone(arr);
// ❌ Comparaison d'objets
{a: 1} === {a: 1} // false
// ✅ Comparaison en profondeur
JSON.stringify({a: 1}) === JSON.stringify({a: 1}) // true (attention aux limites)
Résumé
JavaScript moderne est un langage mature et puissant. Un ingénieur front-end doit maîtriser :
- Types et coercion (connaître les pièges)
- Closures (mécanisme + memory leaks)
- Scope et hoisting (var vs let/const, TDZ)
- Event Loop (tasks, microtasks, rAF)
- this (les 5 règles + arrow functions)
- Prototypes/Classes (héritage prototypal)
- Async (Promises, async/await, error handling)
- Modules (ESM, CommonJS, dynamic imports)
- Memory (GC V8, leaks)
- Design patterns (Module, Observer, Singleton, Factory)