Premiers pas
Hello World & Commentaires
JavaScript s'exécute dans les navigateurs et Node.js. console.log() est la principale sortie de débogage. Les commentaires JSDoc (/** */) fournissent des informations de type et de documentation pour les IDE. Utilisez 'use strict' ou les modules ES pour un analyse plus sûre. Les commentaires sont ignorés à l'exécution.
// Single-line comment
/* Multi-line
comment */
// Console output
console.log("Hello, World!");
console.warn("Warning message");
console.error("Error message");
console.table([{a: 1}, {a: 2}]);
// JSDoc comments (for documentation)
/**
* Adds two numbers
* @param {number} a - First number
* @param {number} b - Second number
* @returns {number} The sum
*/
function add(a, b) { return a + b; }Mode strict & Modules
'use strict' active une analyse plus stricte, capturant les erreurs silencieuses. Les modules ES (import/export) sont la norme moderne ; CommonJS (require/module.exports) est traditionnel à Node.js. Utilisez toujours des modules pour éviter de polluer la portée globale. Les navigateurs prennent en charge <script type='module'>.
"use strict"; // enables strict mode (catches common mistakes)
x = 10; // ReferenceError: x is not defined (strict mode)
// ES Modules (modern)
// import { greet } from './utils.js';
// export function greet(name) { return `Hello, ${name}`; }
// CommonJS (Node.js traditional)
// const fs = require('fs');
// module.exports = { greet: (name) => `Hello, ${name}` };
// Running JS
// Browser: include in <script> or via dev server
// Node.js: node script.js
// Deno: deno run script.tsVariables : let, const, var
Préférez toujours const ; utilisez let uniquement lorsque la réaffectation est nécessaire ; évitez var entièrement. const empêche la réaffectation mais pas la mutation — le contenu des objets/tableaux peut encore changer. let/const ont une portée de bloc ; var a une portée de fonction et est hissée (causant des bugs). La TDZ empêche d'utiliser les variables avant leur déclaration.
// const - cannot be reassigned (use by default)
const PI = 3.14159;
const user = { name: "Alice" };
user.name = "Bob"; // OK - object contents can change
// user = {}; // TypeError - can't reassign const
// let - block-scoped, can be reassigned
let count = 0;
count = 1;
if (true) {
let local = 5; // only accessible in this block
}
// var - function-scoped, hoisted (AVOID in modern code)
var old = "legacy"; // hoisted, can cause bugs
// Temporal Dead Zone (TDZ)
// console.log(x); // ReferenceError
// let x = 5;Types de données & typeof
JavaScript a 7 types primitifs (string, number, bigint, boolean, undefined, null, symbol) et des types de référence (objets, tableaux, fonctions). Les primitives sont immuables et copiées par valeur ; les objets sont mutables et passés par référence. typeof null renvoie 'object' en raison d'un bug historique — utilisez === null pour vérifier null.
// Primitive types (immutable, passed by value)
const str = "hello"; // string
const num = 42; // number (no separate int/float)
const big = 9007199254740993n; // bigint
const bool = true; // boolean
const undef = undefined; // undefined (no value)
const empty = null; // null (intentional empty)
const sym = Symbol("id"); // symbol (unique identifier)
// Reference types (mutable, passed by reference)
const arr = [1, 2, 3]; // object (array)
const obj = { a: 1 }; // object
const fn = function() {}; // object (function)
// typeof operator
console.log(typeof "hi"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (historical bug!)
console.log(typeof []); // "object"
console.log(typeof {}); // "object"
console.log(typeof function(){}); // "function"Conversion de type & Coercion
La coercion de type de JavaScript est notoirement confuse. Utilisez toujours === (égalité stricte) au lieu de == (lâche) pour éviter la coercion inattendue. L'opérateur + concatène si l'un des opérandes est une chaîne ; les autres opérateurs forcent au nombre. Valeurs fausses : false, 0, '', null, undefined, NaN. Utilisez Number.isNaN() pour vérifier NaN (NaN !== NaN).
// Explicit conversion
String(42); // "42"
(42).toString(); // "42"
Number("42"); // 42
Number("3.14"); // 3.14
Number(""); // 0
Number("abc"); // NaN
parseInt("42px"); // 42
parseFloat("3.14abc"); // 3.14
Boolean(0); // false
Boolean(""); // false
Boolean("x"); // true
// Implicit coercion (often confusing)
console.log("5" + 3); // "53" (string concatenation)
console.log("5" - 3); // 2 (numeric subtraction)
console.log("5" * "2"); // 10
console.log(1 + "2" + 3); // "123"
console.log(true + 1); // 2
// Falsy values: false, 0, "", null, undefined, NaN
// Everything else is truthy
if ("0") console.log("truthy"); // runs! non-empty string
// Strict vs loose equality
console.log(1 == "1"); // true (loose, coerces)
console.log(1 === "1"); // false (strict, no coercion)
console.log(null == undefined); // true
console.log(null === undefined); // falseChaînes
Méthodes de chaîne
Les chaînes sont immuables — les méthodes renvoient de nouvelles chaînes. slice() prend en charge les indices négatifs (depuis la fin) ; substring() non. replace() remplace uniquement la première correspondance ; utilisez replaceAll() (ES2021) pour toutes. at() (ES2022) autorise les indices négatifs. split() + join() est la méthode idiomatique pour remplacer des caractères dans une chaîne.
const s = "Hello, World";
// Length & access
console.log(s.length); // 12
console.log(s[0]); // "H"
console.log(s.charAt(0)); // "H"
console.log(s.at(-1)); // "d" (ES2022, negative index)
// Case
console.log(s.toUpperCase()); // "HELLO, WORLD"
console.log(s.toLowerCase()); // "hello, world"
// Search
console.log(s.indexOf("World")); // 7 (-1 if not found)
console.log(s.includes("World")); // true
console.log(s.startsWith("Hello")); // true
console.log(s.endsWith("World")); // true
// Extract
console.log(s.slice(0, 5)); // "Hello"
console.log(s.slice(-5)); // "World"
console.log(s.substring(0, 5)); // "Hello" (no negative)
console.log(s.split(", ")); // ["Hello", "World"]
// Modify
console.log(s.replace("o", "0")); // "Hell0, World"
console.log(s.replaceAll("o", "0")); // "Hell0, W0rld"
console.log(s.trim()); // remove whitespace
console.log(s.padStart(15, "*")); // "***Hello, World"
console.log("a,b,c".split(",").join("-")); // "a-b-c"Littéraux de gabarit
Les littéraux de gabarit (backticks) permettent l'interpolation de chaîne avec ${}, les chaînes multi-lignes et les gabarits étiquetés. Ils sont bien plus lisibles que la concaténation de chaînes. Les gabarits étiquetés vous permettent de traiter les littéraux de gabarit avec une fonction — utilisés par styled-components, graphql-tag, etc.
const name = "Alice";
const age = 30;
// Template literals (backticks)
const greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting);
// Multi-line strings
const html = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
// Expressions inside ${}
console.log(`Next year: ${age + 1}`);
console.log(`Upper: ${name.toUpperCase()}`);
console.log(`${name.length} chars`);
// Tagged templates
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] ? `<b>${values[i]}</b>` : ''), '');
}
const result = highlight`Name: ${name}, Age: ${age}`;
// "Name: <b>Alice</b>, Age: <b>30</b>"Recherche de chaîne & Regex
Les chaînes JavaScript prennent en charge regex via match(), matchAll(), replace(), search() et split(). Les groupes de capture nommés (?<name>...) (ES2018) rendent le regex plus lisible. matchAll() renvoie un itérateur (plus efficace que match() pour les regex globaux). Utilisez .test() pour vérifier si un motif correspond sans extraire.
const text = "The quick brown fox jumps over the lazy dog";
// Search methods
console.log(text.search(/brown/)); // 10 (index of match)
console.log(text.match(/\w+/g)); // ["The","quick","brown",...]
console.log(text.matchAll(/\w+/g)); // iterator of matches
// Replace with regex
console.log(text.replace(/o/g, "0")); // replace all 'o'
console.log(text.replace(/(\w+)/g, "\$1!")); // capture group
// Capture groups
const date = "2024-01-15";
const [, year, month, day] = date.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(year, month, day); // 2024 01 15
// Named capture groups (ES2018)
const m = date.match(/(?<year>\d{4})-(?<month>\d{2})/);
console.log(m.groups.year); // 2024
console.log(m.groups.month); // 01
// Test if pattern matches
const emailRe = /^[^@]+@[^@]+\.[^@]+$/;
console.log(emailRe.test("[email protected]")); // trueItération de chaîne & Spread
Les chaînes sont itérables avec for...of. L'opérateur spread [...] divise une chaîne en caractères — essentiel pour la gestion correcte des emojis (paires de substitution). La comparaison de chaînes est lexicographique par unité de code UTF-16, utilisez donc localeCompare() pour le tri sensible aux paramètres régionaux. Les emojis et certains caractères ont 2 unités de code de long.
const s = "Hello";
// Iterate characters
for (const char of s) {
console.log(char); // H, e, l, l, o
}
// Spread into array
const chars = [...s]; // ["H", "e", "l", "l", "o"]
console.log(chars);
// Spread with map/filter
const upper = [...s].map(c => c.toUpperCase()).join("");
console.log(upper); // HELLO
// String comparison
console.log("a" < "b"); // true (lexicographic)
console.log("apple" < "banana"); // true
console.log("Z" < "a"); // true (uppercase < lowercase in ASCII)
// Locale-aware comparison
console.log("ö".localeCompare("o", "de")); // locale-specific
// Repeat
console.log("ab".repeat(3)); // "ababab"
// Code points (handles emoji correctly)
const emoji = "😀";
console.log(emoji.length); // 2 (surrogate pair!)
console.log([...emoji].length); // 1 (correct)
console.log("😀".codePointAt(0)); // 128512Nombres & Mathématiques
Nombres & Opérateurs
JavaScript a un seul type de nombre (flottant 64 bits) — pas de int/float séparé. BigInt (suffixe n) gère les entiers au-delà de 2^53. L'arithmétique à virgule flottante a des problèmes de précision (0.1 + 0.2 !== 0.3) — utilisez Number.EPSILON pour les comparaisons. ** est l'opérateur d'exponentiation (ES2016).
// JavaScript has one number type (IEEE 754 double)
const int = 42;
const float = 3.14;
const exp = 1e6; // 1000000
const hex = 0xff; // 255
const bin = 0b1010; // 10
const oct = 0o755; // 493
const big = 9007199254740993n; // BigInt (arbitrary precision)
// Arithmetic
console.log(10 / 3); // 3.333...
console.log(10 % 3); // 1 (remainder)
console.log(2 ** 10); // 1024 (exponent)
console.log(Math.floor(10 / 3)); // 3
console.log(Math.trunc(-3.7)); // -3 (toward zero)
// Increment/decrement
let x = 5;
console.log(x++); // 5 (post-increment, returns old)
console.log(++x); // 7 (pre-increment, returns new)
// Floating point issues
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false!
// Fix: use Number.EPSILON or round
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // trueObjet Math
L'objet Math fournit des constantes et fonctions. Toutes les fonctions trigonométriques utilisent des radians. Math.random() renvoie [0, 1) — multipliez et appliquez floor pour des plages d'entiers. Pour l'aléatoire cryptographique, utilisez crypto.getRandomValues(). Math.max/min n'acceptent pas les tableaux directement — déployez-les avec ....
// Constants
console.log(Math.PI); // 3.141592653589793
console.log(Math.E); // 2.718281828459045
console.log(Math.SQRT2); // 1.4142135623730951
// Rounding
console.log(Math.round(3.7)); // 4 (nearest)
console.log(Math.floor(3.7)); // 3 (down)
console.log(Math.ceil(3.2)); // 4 (up)
console.log(Math.trunc(-3.7)); // -3 (toward zero)
console.log(Math.sign(-5)); // -1 (sign: -1, 0, or 1)
// Power & roots
console.log(Math.pow(2, 10)); // 1024
console.log(Math.sqrt(144)); // 12
console.log(Math.cbrt(27)); // 3
console.log(Math.abs(-5)); // 5
// Min/Max
console.log(Math.max(1, 5, 3)); // 5
console.log(Math.min(1, 5, 3)); // 1
console.log(Math.max(...[1, 5, 3])); // 5 (spread array)
// Trigonometry (radians)
console.log(Math.sin(Math.PI / 2)); // 1
console.log(Math.cos(0)); // 1
console.log(Math.PI / 180 * 90); // radians from degrees
// Random
console.log(Math.random()); // 0 to <1
console.log(Math.floor(Math.random() * 100)); // 0-99 integerMéthodes de nombre & Analyse
Spécifiez toujours la radix (base) pour parseInt() — les anciens navigateurs interprètent les zéros de tête comme octaux. Number.isNaN() est fiable ; isNaN() global force (isNaN('abc') est true). toFixed() renvoie une chaîne, pas un nombre. Les nombres au-delà de MAX_SAFE_INTEGER perdent en précision — utilisez BigInt.
// Number methods
const num = 1234.5678;
console.log(num.toFixed(2)); // "1234.57" (string)
console.log(num.toPrecision(3)); // "1.23e+3"
console.log(num.toString(2)); // binary string
console.log((255).toString(16)); // "ff" (hex)
// Number object methods
console.log(Number.isInteger(42)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isNaN(NaN)); // true (reliable)
console.log(Number.isNaN("NaN")); // false (global isNaN would be true)
console.log(Number.parseInt("42px")); // 42
console.log(Number.parseFloat("3.14")); // 3.14
// Parsing strings
console.log(parseInt("42", 10)); // 42 (always specify radix!)
console.log(parseInt("0xff", 16)); // 255
console.log(parseFloat("3.14abc")); // 3.14
// Safe integers
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.isSafeInteger(2 ** 53)); // false
// NaN checks
const result = Number("abc"); // NaN
console.log(Number.isNaN(result)); // true
console.log(result === NaN); // false! NaN !== NaNStructures de données
Tableaux
Les tableaux sont dynamiques, ordonnés et peuvent contenir des types mixtes. push/pop sont O(1) ; shift/unshift/splice sont O(n). find()/findIndex() prennent une fonction prédicat. forEach() ne renvoie rien ; utilisez map() pour transformer. Les tableaux sont des objets — typeof [] est 'object'. Utilisez Array.isArray() pour vérifier.
// Creating arrays
const nums = [1, 2, 3, 4, 5];
const mixed = [1, "hello", true, null];
const empty = new Array(5); // [empty x 5]
// Access & modify
console.log(nums[0]); // 1
console.log(nums.length); // 5
nums[0] = 0; // modify
console.log(nums.at(-1)); // 5 (negative index, ES2022)
// Add/remove
nums.push(6); // add to end, returns new length
nums.pop(); // remove from end, returns element
nums.unshift(0); // add to start
nums.shift(); // remove from start
nums.splice(1, 2); // remove 2 elements at index 1
nums.splice(1, 0, "a"); // insert at index 1
// Search
console.log(nums.indexOf(3)); // index or -1
console.log(nums.includes(3)); // true/false
console.log(nums.find(n => n > 3)); // first match
console.log(nums.findIndex(n => n > 3)); // index of first match
// Iterate
nums.forEach((val, idx) => console.log(idx, val));
for (const [idx, val] of nums.entries()) {
console.log(idx, val);
}Méthodes de tableau (map, filter, reduce)
map/filter/reduce sont la trinité sacrée de la programmation de tableaux fonctionnelle. map transforme, filter sélectionne, reduce agrège. Ils sont chaînables et ne mutent pas l'original (sauf reverse/sort). sort() convertit en chaînes par défaut — fournissez toujours un comparateur pour les nombres. flat() aplatit les tableaux imbriqués.
const nums = [1, 2, 3, 4, 5];
// map - transform each element (returns new array)
const doubled = nums.map(n => n * 2); // [2, 4, 6, 8, 10]
const withIndex = nums.map((n, i) => `${i}:${n}`);
// filter - keep elements that pass test (returns new array)
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
// reduce - accumulate to single value
const sum = nums.reduce((acc, n) => acc + n, 0); // 15
const product = nums.reduce((acc, n) => acc * n, 1); // 120
const max = nums.reduce((a, b) => Math.max(a, b));
// Chaining
const result = nums
.filter(n => n % 2 === 0) // [2, 4]
.map(n => n * 10) // [20, 40]
.reduce((sum, n) => sum + n, 0); // 60
// Other useful methods
console.log(nums.slice(1, 3)); // [2, 3] (copy portion)
console.log(nums.concat([6, 7])); // [1,2,3,4,5,6,7]
console.log([...nums, 6, 7]); // spread (modern)
console.log(nums.reverse()); // reverse in-place
console.log(nums.sort((a, b) => a - b)); // numeric sort
console.log([3, 1, 2].sort()); // [1, 2, 3] (string sort!)
console.log(nums.flat()); // flatten one level
console.log([1, [2, [3]]].flat(Infinity)); // [1, 2, 3]Objets
Les objets sont des collections clé-valeur (les clés sont des chaînes ou des symboles). Notation par point pour les clés statiques, notation par crochets pour les clés dynamiques/spéciales. Les noms de propriété calculés {[expr]: val} sont ES6. Object.keys/values/entries extraient des tableaux ; Object.fromEntries inverse les entries. for...in itère les clés (y compris héritées).
// Object literal
const user = {
name: "Alice",
age: 30,
"is-admin": false, // keys with special chars need quotes
greet() { // method shorthand
return `Hello, I'm ${this.name}`;
}
};
// Access
console.log(user.name); // dot notation
console.log(user["is-admin"]); // bracket notation (for dynamic/special keys)
// Computed property names (ES6)
const key = "dynamic";
const obj = { [key]: "value", [`id_${1}`]: 100 };
// Add/modify/delete
user.email = "[email protected]"; // add
user.age = 31; // modify
delete user["is-admin"]; // delete
// Check property existence
console.log("name" in user); // true
console.log(user.hasOwnProperty("name")); // true
// Iterate
for (const key in user) {
console.log(key, user[key]);
}
// Object methods
console.log(Object.keys(user)); // ["name", "age", ...]
console.log(Object.values(user)); // ["Alice", 31, ...]
console.log(Object.entries(user)); // [["name","Alice"], ...]
console.log(Object.fromEntries([["a", 1]])); // {a: 1}Déstructuration & Spread
La déstructuration extrait les valeurs des objets/tableaux de façon concise. Prend en charge le renommage (key: newName), les valeurs par défaut (= value) et le reste (...rest). Le spread (...) déplie les itérables/objets — excellent pour fusionner et copier superficiellement. Le spread d'objet remplace les clés dupliquées (le dernier gagne). La déstructuration dans les paramètres de fonction est puissante pour la configuration optionnelle.
// Object destructuring
const user = { name: "Alice", age: 30, email: "[email protected]" };
const { name, age } = user; // extract by key
const { name: fullName, email = "N/A" } = user; // rename + default
const { ...rest } = user; // rest pattern
// name -> undefined, fullName -> "Alice", rest -> {age, email}
// Array destructuring
const [a, b, c] = [1, 2, 3];
const [first, , third] = [1, 2, 3]; // skip elements
const [head, ...tail] = [1, 2, 3, 4]; // head=1, tail=[2,3,4]
// Swap variables
let x = 1, y = 2;
[x, y] = [y, x]; // x=2, y=1
// Nested destructuring
const { data: { users } } = response;
const [[a, b], [c, d]] = [[1, 2], [3, 4]];
// Function parameters
function greet({ name, greeting = "Hello" }) {
return `${greeting}, ${name}`;
}
greet({ name: "Alice" }); // "Hello, Alice"
// Spread operator
const arr1 = [1, 2], arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]
const obj1 = { a: 1 }, obj2 = { b: 2 };
const combined = { ...obj1, ...obj2 }; // {a:1, b:2}
const clone = { ...user }; // shallow copyMap, Set, WeakMap
Map autorise tout type de clé (objets, pas seulement des chaînes) et maintient l'ordre d'insertion — contrairement aux objets simples. Set stocke des valeurs uniques — parfait pour la déduplication. Les clés WeakMap/WeakSet sont faiblement référencées (peuvent être collectées par le GC), empêchant les fuites de mémoire. Utilisez Map lorsque vous avez besoin de clés non-chaînes ou d'ajout/suppression fréquents.
// Map - key-value pairs, any key type, maintains insertion order
const map = new Map();
map.set("name", "Alice");
map.set(42, "number key");
map.set({ obj: true }, "object key");
console.log(map.get("name")); // "Alice"
console.log(map.has("name")); // true
console.log(map.size); // 3
map.delete(42);
map.clear();
// Iterate Map
for (const [key, value] of map) {
console.log(key, value);
}
// Set - unique values
const set = new Set([1, 2, 3, 2, 1]);
console.log(set.size); // 3 (duplicates removed)
set.add(4);
set.has(2); // true
set.delete(1);
// Set operations
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // {1,2,3,4}
const intersection = new Set([...a].filter(x => b.has(x))); // {2,3}
const difference = new Set([...a].filter(x => !b.has(x))); // {1}
// WeakMap/WeakSet - keys must be objects, GC-friendly
const weakMap = new WeakMap();
weakMap.set({}, "value"); // key can be garbage collectedFlux de contrôle
If / Else & Ternaire
Utilisez if/else pour la logique complexe ; ternaire pour la sélection simple de valeur. && et || court-circuitent (utile pour les valeurs par défaut/conditionnelles). ?? (coalescence des nuls) vérifie uniquement null/undefined, contrairement à || qui vérifie toutes les valeurs fausses. ?. (chaînage optionnel) accède sûrement aux propriétés imbriquées sans erreurs.
const score = 85;
// if / else if / else
if (score >= 90) {
console.log("A");
} else if (score >= 80) {
console.log("B");
} else {
console.log("C");
}
// Ternary operator (expression)
const grade = score >= 60 ? "pass" : "fail";
// Nested ternary (avoid - hard to read)
const status = score >= 90 ? "excellent"
: score >= 80 ? "good"
: score >= 60 ? "pass"
: "fail";
// Short-circuit evaluation
const name = user?.name || "Anonymous"; // default value
const value = condition && doSomething(); // execute if true
// Nullish coalescing (??) - only null/undefined, not falsy
const count = 0 ?? 10; // 0 (not 10, because 0 is not null/undefined)
const name2 = null ?? "default"; // "default"
// Optional chaining (?.)
const city = user?.address?.city; // undefined if any link is null
const length = user?.name?.length; // undefined if user or name is nullInstruction switch
switch compare avec une égalité stricte (===), donc le type compte. N'oubliez pas break — sans lui, l'exécution passe au cas suivant. Groupez les cas en les empilant (case 6: case 7:). Switch est plus propre que de longues chaînes if/else pour les valeurs discrètes. Le code moderne préfère parfois les tables de recherche d'objets.
const day = 3;
// Traditional switch (use break!)
switch (day) {
case 1:
console.log("Monday");
break; // without break, falls through!
case 2:
console.log("Tuesday");
break;
case 6:
case 7: // multiple cases share code
console.log("Weekend");
break;
default:
console.log("Weekday");
}
// Switch with return (no break needed)
function getColor(type) {
switch (type) {
case "success": return "green";
case "error": return "red";
case "warning": return "yellow";
default: return "gray";
}
}
// Strict equality (===)
switch (1) {
case "1": console.log("string"); // NOT matched
case 1: console.log("number"); // matched
}Boucles
Utilisez for...of pour les tableaux/chaînes (valeurs), for...in pour les objets (clés) — jamais for...in sur les tableaux (itère les indices comme chaînes et inclut le prototype). while vérifie avant d'exécuter ; do...while s'exécute au moins une fois. break sort, continue passe au suivant. Utilisez .entries() pour obtenir index+valeur avec for...of.
// for loop
for (let i = 0; i < 5; i++) {
console.log(i);
}
// for...of (iterable values - arrays, strings, maps)
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
// for...in (object keys - AVOID for arrays!)
const user = { name: "Alice", age: 30 };
for (const key in user) {
console.log(key, user[key]);
}
// while
let count = 0;
while (count < 3) {
console.log(count);
count++;
}
// do...while (runs at least once)
let i = 0;
do {
console.log(i);
i++;
} while (i < 3);
// break & continue
for (let i = 0; i < 10; i++) {
if (i === 5) break; // exit loop
if (i % 2 === 0) continue; // skip iteration
console.log(i);
}
// Iterate with index
for (const [index, value] of fruits.entries()) {
console.log(index, value);
}Itérateurs & Générateurs
Les itérateurs implémentent next() renvoyant {value, done}. Les générateurs (function*) simplifient la création d'itérateur avec yield — ils suspendent l'exécution et reprennent sur next(). Les générateurs sont paresseux (calculent à la demande) et peuvent être infinis. Utilisez-les pour les itérables personnalisés, les séquences et les flux async.
// Iterable protocol (Symbol.iterator)
const range = {
from: 1, to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
}
};
}
};
for (const num of range) console.log(num); // 1, 2, 3, 4, 5
console.log([...range]); // [1, 2, 3, 4, 5]
// Generator function (function*)
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next().value); // 3
// Generator with yield*
function* nested() {
yield 1;
yield* [2, 3, 4]; // delegate to another iterable
yield 5;
}Fonctions
Déclarations & Expressions de fonction
Les déclarations de fonction sont hissées (peuvent être appelées avant la définition) ; les expressions non. Les fonctions fléchées sont concises et n'ont pas leur propre 'this' (elles héritent de la portée englobante). Les IIFE créent des portées privées (moins nécessaires avec les modules). Les fonctions sont de première classe — passez-les en arguments, renvoyez-les, stockez-les dans des variables.
// Function declaration (hoisted - can be called before definition)
greet("Alice"); // works (hoisted)
function greet(name) {
return `Hello, ${name}!`;
}
// Function expression (not hoisted)
const greet2 = function(name) {
return `Hi, ${name}!`;
};
// Named function expression (for recursion/stack traces)
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1);
};
// Arrow function (ES6) - concise, no own this
const add = (a, b) => a + b;
const square = x => x * x;
const greet3 = () => "Hello!";
const log = x => { console.log(x); }; // block body needs return
// IIFE (Immediately Invoked Function Expression)
const result = (function() {
const private = "secret";
return private.toUpperCase();
})();
// Functions are first-class (can be passed/returned)
function apply(fn, value) {
return fn(value);
}
console.log(apply(square, 5)); // 25Fonctions fléchées & this
Les fonctions fléchées n'ont pas leur propre 'this', 'arguments', 'super' ou 'new.target' — elles héritent de la portée englobante. Cela les rend parfaites pour les rappels (surtout dans les méthodes de classe). Mais elles ne peuvent pas être utilisées comme constructeurs ou méthodes nécessitant leur propre 'this'. Utilisez des fonctions régulières pour les méthodes d'objet.
// Arrow function variations
const add = (a, b) => a + b; // implicit return
const greet = name => `Hi ${name}`; // single param, no parens
const log = () => console.log("hi"); // no params
const obj = (x, y) => ({ x, y }); // return object needs parens
const multi = (a, b) => { // block body
const sum = a + b;
return sum * 2;
};
// Arrow functions don't have their own 'this'
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // 'this' refers to Timer instance
console.log(this.seconds);
}, 1000);
}
// Regular function has its own 'this'
function Counter() {
this.count = 0;
// Regular function: 'this' is undefined (strict) or global
document.addEventListener("click", function() {
// this.count++; // ERROR: this is not Counter
});
// Arrow function: 'this' is Counter
document.addEventListener("click", () => {
this.count++; // works!
});
}
// Arrow functions can't be constructors
// const obj = new arrowFunc(); // TypeErrorFermetures
Les fermetures sont des fonctions qui 'se souviennent' des variables de leur portée de définition, même après que cette portée soit terminée. Elles permettent la confidentialité des données (motif module), la mémorisation, le curry et l'application partielle. Toute fonction en JavaScript est une fermeture. La fonction interne garde une référence aux variables externes, pas une copie.
// A closure is a function that remembers its outer variables
function makeCounter() {
let count = 0; // private variable
return function() {
return ++count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count is private - can't access directly
// Module pattern (pre-ES6)
const calculator = (function() {
const result = 0; // private
return {
add(x) { return result + x; },
multiply(x) { return result * x; }
};
})();
// Practical: memoization
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (!(key in cache)) {
cache[key] = fn.apply(this, args);
}
return cache[key];
};
}
const slowFib = memoize(n =>
n < 2 ? n : slowFib(n - 1) + slowFib(n - 2)
);
// Currying with closures
const multiply = a => b => a * b;
const double = multiply(2);
console.log(double(5)); // 10Arguments & Rest/Spread
Les paramètres par défaut fournissent des valeurs de secours. Les paramètres rest (...name) collectent les arguments supplémentaires dans un vrai tableau — préférez à l'objet 'arguments' hérité. Le spread (...) déplie un tableau en arguments individuels. La déstructuration dans les paramètres active des objets de configuration nommés et optionnels — un motif d'API courant.
// Default parameters
function greet(name = "Guest", greeting = "Hello") {
return `${greeting}, ${name}!`;
}
greet(); // "Hello, Guest!"
greet("Alice"); // "Hello, Alice!"
greet("Bob", "Hi"); // "Hi, Bob!"
// Rest parameters (...args collects into array)
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
function log(tag, ...args) {
console.log(tag, args);
}
// Spread (opposite of rest)
const nums = [1, 2, 3];
console.log(Math.max(...nums)); // 3 (spread array as args)
console.log(sum(...nums)); // 6
// arguments object (old way, avoid in modern code)
function old() {
console.log(arguments); // array-like, not a real array
const args = Array.from(arguments); // convert
}
// Destructuring parameters
function process({ name, age = 0 } = {}) {
console.log(name, age);
}
process({ name: "Alice" }); // Alice 0
process(); // undefined 0 (default empty object)POO & Classes
Classes & Constructeur
Les classes ES6 sont du sucre syntaxique sur les prototypes. Les champs privés (#name) sont ES2022 et véritablement privés (contrairement à la convention _name). Les getters/setters permettent des propriétés calculées. Les membres statiques appartiennent à la classe, pas aux instances. Les champs de classe (name = value) initialisent les propriétés d'instance sans constructeur.
class Person {
// Fields (class fields proposal - ES2022)
species = "human"; // instance field
// Private fields (ES2022)
#ssn = "secret"; // truly private
// Static field/method
static count = 0;
// Constructor
constructor(name, age) {
this.name = name;
this.age = age;
Person.count++;
}
// Instance method
greet() {
return `Hello, I'm ${this.name}`;
}
// Getter
get info() {
return `${this.name}, ${this.age}`;
}
// Setter
set age(value) {
if (value < 0) throw new Error("Invalid age");
this._age = value;
}
get age() {
return this._age;
}
// Static method
static create(name) {
return new Person(name, 0);
}
}
const alice = new Person("Alice", 30);
console.log(alice.greet()); // Hello, I'm Alice
console.log(alice.info); // Alice, 30
console.log(Person.count); // 1
// alice.#ssn; // SyntaxError - private!Héritage & Polymorphisme
extends crée l'héritage ; super() appelle le constructeur parent (requis avant d'utiliser 'this'). Remplacez les méthodes en les redéfinissant. JavaScript est à héritage simple, mais les mixins (fabriques de classes) fournissent la composition. instanceof vérifie la chaîne de prototypes. Le polymorphisme fonctionne via la redéfinition de méthode.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
// Static method can be inherited
static create(type, name) {
return new type(name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // must call super() first
this.breed = breed;
}
speak() {
return `${this.name} barks`; // override
}
fetch() {
return `${this.name} fetches`;
}
}
class Cat extends Animal {
speak() {
return `${this.name} meows`;
}
}
// Polymorphism
const animals = [new Dog("Rex", "Lab"), new Cat("Whiskers")];
animals.forEach(a => console.log(a.speak()));
// Rex barks
// Whiskers meows
// instanceof checks
const dog = new Dog("Buddy", "Poodle");
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog.constructor.name); // "Dog"
// Mixins (multiple inheritance alternative)
const Walker = (Base) => class extends Base {
walk() { return `${this.name} walks`; }
};
class Robot extends Walker(Animal) {}Prototypes
JavaScript utilise l'héritage prototypal — les objets héritent d'autres objets via une chaîne de prototypes. __proto__ est déprécié ; utilisez Object.getPrototypeOf/setPrototypeOf. Les classes sont du sucre syntaxique sur ce système. Modifier les prototypes intégrés (Array.prototype) est dangereux — cela peut casser le code. Préférez la composition à l'héritage profond.
// Every object has a prototype (chain of inheritance)
const obj = {};
console.log(obj.__proto__); // Object.prototype
console.log(Object.getPrototypeOf(obj)); // preferred
// Constructor function (pre-class syntax)
function OldPerson(name) {
this.name = name;
}
OldPerson.prototype.greet = function() {
return `Hi, ${this.name}`;
};
const p = new OldPerson("Alice");
console.log(p.greet()); // Hi, Alice
// Prototype chain
// p -> OldPerson.prototype -> Object.prototype -> null
// Adding to prototype (affects all instances)
Array.prototype.last = function() {
return this[this.length - 1];
};
console.log([1, 2, 3].last()); // 3
// Object.create (prototypal inheritance)
const animal = { type: "unknown" };
const dog = Object.create(animal);
dog.type = "dog";
console.log(dog.type); // "dog" (own property)
// Check own vs inherited
console.log(dog.hasOwnProperty("type")); // true
console.log("type" in dog); // true (includes inherited)
// Get prototype chain
let proto = Object.getPrototypeOf(dog);
while (proto) {
console.log(proto);
proto = Object.getPrototypeOf(proto);
}Gestion des erreurs
Try / Catch / Finally
try/catch/finally gère les exceptions. catch lie l'objet d'erreur (qui a .message et .stack). Utilisez instanceof pour gérer différents types d'erreurs. Relancez toujours les erreurs inconnues après avoir géré celles attendues. Créez des erreurs personnalisées en étendant Error pour la gestion d'erreurs spécifique à l'application.
try {
const result = JSON.parse('{"invalid json"');
} catch (error) {
console.error("Parse error:", error.message);
console.error("Stack:", error.stack);
} finally {
console.log("Always runs");
}
// Catch specific error types
try {
const data = JSON.parse(input);
if (!data.name) throw new TypeError("name is required");
} catch (error) {
if (error instanceof SyntaxError) {
console.log("JSON syntax error");
} else if (error instanceof TypeError) {
console.log("Type error:", error.message);
} else {
throw error; // re-throw unknown errors
}
}
// Error properties
const err = new Error("Something went wrong");
err.code = "CUSTOM_ERROR";
err.statusCode = 500;
throw err;
// Built-in error types
// Error, TypeError, RangeError, ReferenceError
// SyntaxError, URIError, EvalErrorErreurs personnalisées
Étendez Error pour créer des types d'erreur personnalisés avec du contexte supplémentaire (champs, codes). Définissez toujours this.name pour correspondre au nom de la classe. Utilisez instanceof pour capturer des types d'erreur spécifiques. Le chaînage d'erreurs (ES2022 { cause }) préserve l'erreur originale pour le débogage. Une bonne hiérarchie d'erreurs rend la gestion d'erreurs précise.
// Custom error class
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class DatabaseError extends Error {
constructor(message, query) {
super(message);
this.name = "DatabaseError";
this.query = query;
}
}
// Usage
function validateUser(user) {
if (!user.email) {
throw new ValidationError("email", "Email is required");
}
if (!user.email.includes("@")) {
throw new ValidationError("email", "Invalid email format");
}
}
// Handling custom errors
try {
validateUser({ name: "Alice" });
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation failed: ${error.field} - ${error.message}`);
} else if (error instanceof DatabaseError) {
console.log(`DB error in query: ${error.query}`);
} else {
console.log("Unexpected error:", error);
}
}
// Error chaining (ES2022)
try {
throw new Error("Original cause");
} catch (cause) {
throw new Error("Failed to process", { cause });
}Async & Promesses
Rappels
Les rappels sont des fonctions passées pour être appelées plus tard. La convention erreur-d'abord (err, data) est standard dans Node.js. Les rappels imbriqués créent l'enfer des rappels — du code profondément imbriqué, difficile à lire. Les Promesses et async/await résolvent cela. setTimeout/setInterval sont des APIs courantes basés sur des rappels.
// Callback pattern (old way)
function fetchData(url, callback) {
setTimeout(() => {
callback(null, { data: "result" });
}, 1000);
}
// Callback with error-first convention (Node.js style)
fetchData("/api", (error, data) => {
if (error) {
console.error(error);
return;
}
console.log(data);
});
// Callback hell (pyramid of doom)
fetchUser(userId, (err, user) => {
if (err) return handleError(err);
fetchPosts(user.id, (err, posts) => {
if (err) return handleError(err);
fetchComments(posts[0].id, (err, comments) => {
if (err) return handleError(err);
// deeply nested...
});
});
});
// Event listeners are callbacks
button.addEventListener("click", (event) => {
console.log("Clicked!", event.target);
});
// setTimeout / setInterval
setTimeout(() => console.log("After 1s"), 1000);
const id = setInterval(() => console.log("tick"), 1000);
clearInterval(id); // stopPromesses
Les Promesses représentent des valeurs futures avec trois états : en attente, tenue, rejetée. .then() gère le succès, .catch() gère les erreurs, .finally() s'exécute toujours. Promise.all() attend toutes (échec rapide) ; allSettled() attend toutes (n'échoue jamais) ; race() renvoie la première réglée ; any() renvoie la première réussie.
// Creating a promise
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Data received");
} else {
reject(new Error("Failed"));
}
});
// Consuming a promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log("Done"));
// Chaining
fetch("/api/users")
.then(response => response.json())
.then(users => users.filter(u => u.active))
.then(active => console.log(active))
.catch(err => console.error("Error:", err));
// Promise.all - wait for all (fails if any fails)
Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json())
]).then(([users, posts]) => {
console.log(users, posts);
});
// Promise.allSettled - wait for all (never fails)
Promise.allSettled([p1, p2]).then(results => {
results.forEach(r => {
if (r.status === "fulfilled") console.log(r.value);
else console.log(r.reason);
});
});
// Promise.race - first to settle wins
Promise.race([p1, p2]).then(first => console.log(first));
// Promise.any - first to succeed
Promise.any([p1, p2]).then(first => console.log(first));Async / Await
async/await est du sucre syntaxique sur les promesses — fait que le code async ressemble à du synchrone. 'await' suspend la fonction jusqu'à ce que la promesse se règle. Enveloppez toujours await dans try/catch pour la gestion d'erreurs. Utilisez Promise.all() pour des opérations parallèles (plus rapide qu'un await séquentiel dans une boucle). L'await de haut niveau fonctionne dans les modules ES.
// async function always returns a Promise
async function fetchData() {
// await pauses until the promise resolves
const response = await fetch("/api/users");
const data = await response.json();
return data;
}
// Error handling with try/catch
async function getUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return await res.json();
} catch (error) {
console.error("Failed:", error);
return null;
}
}
// Sequential vs parallel
async function sequential() {
const a = await fetch("/api/a"); // waits for a
const b = await fetch("/api/b"); // then waits for b
return [a, b];
}
async function parallel() {
const [a, b] = await Promise.all([ // both at once
fetch("/api/a"),
fetch("/api/b")
]);
return [a, b];
}
// Top-level await (ES2022, in modules)
// const data = await fetch("/api").then(r => r.json());
// Iterating with async
async function processUrls(urls) {
for (const url of urls) {
const data = await fetch(url);
console.log(data);
}
}
// Promise.all with map (parallel)
async function fetchAll(urls) {
return Promise.all(urls.map(url => fetch(url)));
}Manipulation du DOM
Sélectionner & Modifier des éléments
querySelector/querySelectorAll (sélecteurs CSS) sont la méthode moderne pour sélectionner des éléments. textContent est plus sûr qu'innerHTML (empêche XSS). classList fournit add/remove/toggle/contains pour les classes. dataset accède aux attributs data-*. Assainissez toujours l'entrée utilisateur avant de définir innerHTML.
// Selecting elements
const el = document.querySelector("#app"); // first match
const all = document.querySelectorAll(".item"); // all matches (NodeList)
const byId = document.getElementById("app");
const byClass = document.getElementsByClassName("item"); // HTMLCollection
// Modifying content
el.textContent = "Hello"; // text only (safe from XSS)
el.innerHTML = "<b>Bold</b>"; // HTML (XSS risk with user input!)
el.innerText = "Visible text"; // respects CSS visibility
// Attributes
el.setAttribute("data-id", "123");
const id = el.getAttribute("data-id");
el.removeAttribute("disabled");
el.dataset.id; // access data-* attributes
el.id = "new-id";
el.className = "active highlighted";
el.classList.add("active");
el.classList.remove("old");
el.classList.toggle("hidden");
el.classList.contains("active");
// Styles
el.style.color = "red";
el.style.backgroundColor = "blue"; // camelCase
el.style.cssText = "color: red; font-size: 16px;";
// Creating elements
const div = document.createElement("div");
div.textContent = "New element";
div.classList.add("box");
document.body.appendChild(div);
document.body.prepend(div); // add to beginning
document.body.insertBefore(div, referenceEl);Événements
addEventListener est préféré aux propriétés on<event> (autorise plusieurs écouteurs). La délégation d'événement (écoute sur un parent) est efficace pour les éléments ajoutés dynamiquement. e.target est ce qui a été cliqué ; e.currentTarget est l'élément avec l'écouteur. preventDefault() arrête le comportement par défaut ; stopPropagation() arrête le bouillonnement.
// Add event listener
button.addEventListener("click", (event) => {
console.log("Clicked!", event.target);
console.log("Current target:", event.currentTarget);
});
// Common events
// click, dblclick, mousedown, mouseup, mousemove
// keydown, keyup, keypress
// submit, change, input, focus, blur
// load, DOMContentLoaded, resize, scroll
// Event object properties
input.addEventListener("keydown", (e) => {
console.log(e.key); // "Enter", "a", etc.
console.log(e.code); // "KeyA", "Enter"
console.log(e.ctrlKey); // true if Ctrl held
e.preventDefault(); // stop default behavior
e.stopPropagation(); // stop bubbling
});
// Event delegation (efficient for many elements)
document.addEventListener("click", (e) => {
if (e.target.matches(".delete-btn")) {
const id = e.target.dataset.id;
deleteItem(id);
}
});
// Custom events
const customEvent = new CustomEvent("userLogin", {
detail: { userId: 123 }
});
element.dispatchEvent(customEvent);
element.addEventListener("userLogin", (e) => {
console.log("User logged in:", e.detail.userId);
});
// Remove listener (must be same function reference)
const handler = () => console.log("click");
button.addEventListener("click", handler);
button.removeEventListener("click", handler);Modules & JSON
Modules ES
Les modules ES (import/export) sont la norme moderne, pris en charge dans les navigateurs et Node.js. Export par défaut (un par module) vs exports nommés (multiples). L'import dynamique () active le chargement paresseux. Les modules sont toujours en mode strict et ont leur propre portée. Utilisez type='module' dans les balises de script HTML.
// math.js - exporting
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator { /* ... */ }
// Default export (one per module)
export default function greet(name) {
return `Hello, ${name}`;
}
// main.js - importing
import greet from "./math.js"; // default
import { add, PI } from "./math.js"; // named
import * as math from "./math.js"; // namespace
import { add as plus } from "./math.js"; // rename
import greet, { add } from "./math.js"; // default + named
// Dynamic import (returns Promise)
const module = await import("./math.js");
console.log(module.add(2, 3));
// Conditional/dynamic loading
if (featureEnabled) {
const { default: Feature } = await import("./feature.js");
new Feature();
}
// Re-export
export { add } from "./math.js";
export * from "./utils.js";
// In HTML
// <script type="module" src="app.js"></script>
// Modules are deferred and in strict mode by defaultJSON
JSON.stringify() convertit les valeurs JS en chaînes JSON ; JSON.parse() inverse. Utilisez replacers/revivers pour filtrer ou transformer lors de la conversion. JSON ne prend pas en charge les fonctions, undefined, Dates (elles deviennent des chaînes) ou les références circulaires. fetch().json() analyse les réponses JSON automatiquement. Enveloppez toujours JSON.parse dans try/catch pour les entrées non fiables.
// JSON is the standard data interchange format
// JSON supports: string, number, boolean, null, array, object
// JavaScript object to JSON string
const user = { name: "Alice", age: 30, active: true };
const jsonStr = JSON.stringify(user);
// '{"name":"Alice","age":30,"active":true}'
// Pretty print
const pretty = JSON.stringify(user, null, 2);
// {
// "name": "Alice",
// "age": 30,
// "active": true
// }
// JSON string to JavaScript object
const parsed = JSON.parse('{"name":"Bob","age":25}');
console.log(parsed.name); // "Bob"
// Replacer function (filter/transform during stringify)
const filtered = JSON.stringify(user, (key, value) => {
if (key === "age") return undefined; // exclude
return value;
});
// Reviver function (transform during parse)
const data = JSON.parse(jsonStr, (key, value) => {
if (key === "date") return new Date(value);
return value;
});
// Fetch JSON from API
async function getUsers() {
const res = await fetch("/api/users");
return res.json(); // parses JSON automatically
}
// JSON limitations
// - No functions, undefined, or dates
// - No circular references (throws error)
// - Keys must be strings (with quotes)Date & Heure
JavaScript Date est notoirement maladroit — les mois sont indexés à 0 (Janvier = 0), les jours sont indexés à 1. Les objets Date sont mutables. toISOString() donne UTC ; toLocaleString() donne l'heure locale. Pour un travail sérieux sur les dates, utilisez une bibliothèque comme date-fns ou dayjs. Intl.DateTimeFormat fournit un formatage sensible aux paramètres régionaux.
// Create dates
const now = new Date();
const specific = new Date("2024-01-15T10:30:00");
const fromMs = new Date(1705315200000);
const fromParts = new Date(2024, 0, 15, 10, 30); // month is 0-indexed!
// Get components
console.log(now.getFullYear()); // 2024
console.log(now.getMonth()); // 0-11 (January = 0!)
console.log(now.getDate()); // 1-31
console.log(now.getDay()); // 0-6 (Sunday = 0)
console.log(now.getHours()); // 0-23
console.log(now.getTime()); // milliseconds since epoch
// Set components
now.setFullYear(2025);
now.setMonth(11); // December
// Formatting
console.log(now.toISOString()); // "2024-01-15T10:30:00.000Z"
console.log(now.toLocaleDateString()); // "1/15/2024" (locale)
console.log(now.toLocaleString()); // "1/15/2024, 10:30:00 AM"
// Timestamps
const start = Date.now(); // milliseconds since epoch
// ... do work ...
console.log(`Took ${Date.now() - start}ms`);
// Intl for formatting (modern)
const formatter = new Intl.DateTimeFormat("zh-CN", {
year: "numeric", month: "long", day: "numeric"
});
console.log(formatter.format(now)); // "2024年1月15日"Fonctionnalités ES6+
let, const & Portée de bloc
Préférez const par défaut, let lorsque la réaffectation est nécessaire, et évitez var entièrement. const empêche la réaffectation mais les objets/tableaux restent mutables. let et const ont une portée de bloc et vivent dans la zone morte temporelle avant la déclaration (contrairement à var qui est hissé comme undefined). Cela empêche de nombreux bugs subtils.
// var is function-scoped (hoisted, leaks out of blocks)
// let and const are block-scoped (stay inside {})
{
var x = 1; // accessible outside the block
let y = 2; // block-scoped
const z = 3; // block-scoped, cannot reassign
}
console.log(x); // 1
// console.log(y); // ReferenceError
// const prevents reassignment, NOT mutation
const arr = [1, 2, 3];
arr.push(4); // OK — mutating the array
// arr = [5]; // TypeError — reassigning const
// Temporal Dead Zone: let/const can't be used before declaration
// console.log(a); // ReferenceError (not undefined like var)
let a = 10;Fonctions fléchées & this
Les fonctions fléchées sont concises et héritent 'this' de la portée englobante — parfaites pour les rappels et les méthodes nécessitant le 'this' externe. Mais elles ne peuvent pas être utilisées comme constructeurs et n'ont pas d'objet 'arguments'. N'utilisez pas de fonctions fléchées pour les méthodes d'objet si vous avez besoin que 'this' fasse référence à l'objet (utilisez des méthodes régulières à la place).
// Arrow functions: concise syntax, lexically bound 'this'
const add = (a, b) => a + b;
const square = x => x * x; // single param, no parens
const greet = name => `Hello, ${name}`; // template literal
const noop = () => {}; // no params, empty body
// Returning an object literal needs parens
const makeUser = (name, age) => ({ name, age });
// Arrow functions DON'T have their own 'this' — they inherit it
function Counter() {
this.count = 0;
setInterval(() => {
this.count++; // 'this' is the Counter instance (lexical)
console.log(this.count);
}, 1000);
}
// Regular function would lose 'this' (it'd be window/undefined)Affectation déstructurante
La déstructuration extrait les valeurs des objets/tableaux dans des variables en une ligne — plus propre que l'accès manuel aux propriétés. La déstructuration d'objet utilise { key }, la déstructuration de tableau utilise [index]. Prend en charge le renommage (key: alias), les valeurs par défaut (key = default), le reste (...rest) et les motifs imbriqués. Largement utilisée dans les props React et les paramètres de fonction.
// Object destructuring
const user = { name: "Alice", age: 30, city: "NYC" };
const { name, age } = user;
console.log(name, age); // Alice 30
// Rename and default values
const { name: fullName, country = "USA" } = user;
console.log(fullName, country); // Alice USA
// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first, second, rest); // 1 2 [3,4,5]
// Swap variables (no temp needed!)
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
// Destructuring in function parameters
function greet({ name, greeting = "Hello" }) {
console.log(`${greeting}, ${name}`);
}
greet({ name: "Bob" }); // Hello, Bob
// Nested destructuring
const { data: { results } } = response;Opérateurs Spread & Rest
L'opérateur ... est 'spread' lors de l'expansion (dans les tableaux/objets/appels) et 'rest' lors de la collecte (dans les params/la déstructuration). Le spread crée des copies superficielles et fusionne des objets (les clés postérieures remplacent les antérieures). Les paramètres rest remplacent l'ancien objet 'arguments' et sont de vrais tableaux. Les deux sont des idiomes JS modernes essentiels.
// Spread (...) expands iterables into individual elements
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5] (copy + append)
const merged = [...arr1, ...arr2];
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 } (shallow copy)
const updated = { ...obj1, b: 99 }; // override b: { a: 1, b: 99 }
// Rest (...) collects multiple elements into an array
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
// Rest in destructuring
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1 [2, 3, 4]
// Spread in function calls
Math.max(...[1, 5, 3]); // 5 (same as Math.max(1, 5, 3))Littéraux de gabarit & Gabarits étiquetés
Les littéraux de gabarit (backticks) prennent en charge les chaînes multi-lignes et l'interpolation ${} — bien plus propre que la concaténation de chaînes. Les gabarits étiquetés laissent une fonction traiter les parties littérales et les valeurs interpolées, permettant un formatage personnalisé, l'assainissement (ex. échapper HTML) ou l'i18n. Populaire dans styled-components et graphql-tag.
// Template literals: backticks, multi-line, interpolation
const name = "Alice";
const msg = `Hello ${name},
this spans
multiple lines`;
// Expressions inside ${}
const price = 19.99;
const tax = 0.08;
console.log(`Total: ${(price * (1 + tax)).toFixed(2)}`); // Total: 21.59
// Nested template literals
const items = ["apple", "banana"];
const html = `
<ul>
${items.map(i => `<li>${i}</li>`).join("")}
</ul>
`;
// Tagged templates: function processes the literal
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] ? `**${values[i]}**` : ""), "");
}
const result = highlight`Name: ${name}, Age: ${30}`;
// "Name: **Alice**, Age: **30**"Chaînage optionnel & Coalescence des nuls
Le chaînage optionnel (?.) court-circuite vers undefined si une partie de la chaîne est null/undefined — élimine les vérifications manuelles verbeuses. La coalescence des nuls (??) fournit des valeurs par défaut UNIQUEMENT pour null/undefined, contrairement à || qui remplace également 0, '' et false. Ensemble, ils gèrent sûrement les motifs de 'données manquantes' les plus courants. Disponible depuis ES2020.
// Optional chaining (?.): safely access nested properties
const user = { profile: { name: "Alice" } };
console.log(user?.profile?.name); // "Alice"
console.log(user?.settings?.theme); // undefined (no error!)
console.log(user?.profile?.address?.city); // undefined
// Without ?., you'd write:
// user && user.profile && user.profile.name
// Optional method calls
const result = obj?.method?.();
const firstChar = str?.[0];
// Nullish coalescing (??): default only for null/undefined
const theme = user.settings?.theme ?? "light"; // "light"
const count = 0 ?? 10; // 0 (NOT 10 — ?? keeps falsy but valid values)
const name = "" ?? "Anonymous"; // "" (empty string is kept!)
// vs || which treats all falsy values (0, "", false) as missing
const count2 = 0 || 10; // 10 (probably not what you want!)Événements & Gestion d'événements
Bases d'addEventListener
addEventListener est la méthode moderne pour lier des événements — elle permet plusieurs gestionnaires, prend en charge la délégation d'événement et offre des options comme once, passive et capture. Gardez toujours une référence au gestionnaire si vous devez le supprimer (les fonctions anonymes ne peuvent pas être supprimées). L'objet d'événement transporte target, currentTarget, preventDefault() et stopPropagation().
// Modern event binding (preferred over onclick=)
const button = document.querySelector("#myBtn");
button.addEventListener("click", function(event) {
console.log("Clicked!", event.target);
event.preventDefault(); // stop default action (e.g., form submit)
});
// Multiple listeners can be attached
button.addEventListener("click", handler1);
button.addEventListener("click", handler2);
// Remove a specific listener (must be same reference)
button.removeEventListener("click", handler1);
// Common event types
// "click", "dblclick", "mousedown", "mouseup", "mousemove"
// "keydown", "keyup", "keypress"
// "submit", "change", "input", "focus", "blur"
// "load", "DOMContentLoaded", "resize", "scroll"
// Once option: auto-remove after first trigger
button.addEventListener("click", handler, { once: true });Délégation d'événement
La délégation d'événement attache un écouteur à un parent qui gère les événements de tous les enfants via le bouillonnement d'événement. Utilisez event.target.matches(selector) pour filtrer. C'est bien plus efficace que de lier à chaque enfant et gère automatiquement les éléments ajoutés dynamiquement. Le compromis : le parent doit être un ancêtre commun qui existe toujours.
// Delegate events to a parent instead of each child
// Efficient for dynamically added elements
const list = document.querySelector("#item-list");
list.addEventListener("click", (event) => {
// event.target is the actual element clicked
if (event.target.matches("li.item")) {
console.log("Clicked:", event.target.textContent);
event.target.classList.toggle("selected");
}
});
// Now dynamically added items automatically work
list.insertAdjacentHTML("beforeend", "<li class='item'>New Item</li>");
// Benefits:
// 1. One listener instead of many (memory efficient)
// 2. Works for elements added after binding
// 3. No need to re-bind when DOM changesPropagation d'événement (bouillonnement & capture)
Les événements se propagent en trois phases : capture (descendant), cible et bouillonnement (montant, par défaut). La plupart des gestionnaires s'exécutent dans la phase de bouillonnement. stopPropagation() empêche l'événement d'atteindre les éléments parents ; stopImmediatePropagation() arrête également les autres gestionnaires sur le même élément. Utilisez la capture (troisième argument true) pour les gestionnaires qui doivent s'exécuter avant les gestionnaires enfants.
<div id="outer">
<div id="inner">
<button id="btn">Click</button>
</div>
</div>
// Events flow in three phases:
// 1. Capturing: top -> target (window -> document -> ... -> target)
// 2. Target: at the target element
// 3. Bubbling: target -> top (default phase most handlers run in)
// Bubbling (default): child fires first, then parents
btn.addEventListener("click", () => console.log("button"));
inner.addEventListener("click", () => console.log("inner"));
outer.addEventListener("click", () => console.log("outer"));
// Click button logs: button -> inner -> outer
// stopPropagation: prevent bubbling to parents
btn.addEventListener("click", (e) => {
e.stopPropagation();
console.log("only button");
});
// Capturing phase (third arg = true)
outer.addEventListener("click", handler, true); // runs during capture
// stopImmediatePropagation: stop other handlers on SAME element tooÉvénements personnalisés
CustomEvent vous permet de créer des événements spécifiques à l'application avec des données de charge utile dans la propriété 'detail'. Combiné avec dispatchEvent, cela active un motif pub/sub pour découpler les composants — les modules communiquent sans références directes. Utilisez une convention de nommage comme 'namespace:action' pour éviter les collisions. C'est la fondation de nombreux frameworks d'éléments personnalisés.
// Create and dispatch custom events
const event = new CustomEvent("userLoggedIn", {
detail: { userId: 42, name: "Alice" },
bubbles: true, // allow bubbling
});
document.dispatchEvent(event);
// Listen for the custom event
document.addEventListener("userLoggedIn", (e) => {
console.log("User logged in:", e.detail.userId, e.detail.name);
});
// Practical: decoupled communication between components
class Cart {
constructor() {
this.items = [];
}
add(item) {
this.items.push(item);
window.dispatchEvent(new CustomEvent("cart:updated", {
detail: { count: this.items.length }
}));
}
}
// Any module can listen without knowing about Cart internals
window.addEventListener("cart:updated", (e) => {
updateBadge(e.detail.count);
});Événements clavier & formulaire
Les événements clavier donnent e.key (touche logique comme 'a', 'Enter') et e.code (touche physique comme 'KeyA'). Utilisez e.key pour la plupart de la logique. La soumission de formulaire nécessite toujours preventDefault() pour arrêter le rechargement de page. FormData + Object.fromEntries collecte facilement les données de formulaire. 'input' se déclenche en continu ; 'change' se déclenche lorsque le champ perd le focus — choisissez selon le moment où vous voulez la validation.
// Keyboard events
document.addEventListener("keydown", (e) => {
console.log(e.key, e.code); // e.key="a", e.code="KeyA"
if (e.key === "Escape") closeModal();
if (e.ctrlKey && e.key === "s") { e.preventDefault(); save(); }
});
// Form events
const form = document.querySelector("#myForm");
form.addEventListener("submit", (e) => {
e.preventDefault(); // stop page reload
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log(data); // { username: "...", email: "..." }
});
// Input validation on change/blur
const emailInput = document.querySelector("#email");
emailInput.addEventListener("blur", () => {
if (!emailInput.value.includes("@")) {
emailInput.setCustomValidity("Enter a valid email");
} else {
emailInput.setCustomValidity("");
}
});
// Change vs input:
// 'input' fires on every keystroke; 'change' fires on blurFetch API & AJAX
fetch de base (GET)
fetch() est le remplaçant moderne de XMLHttpRequest — basé sur des promesses et plus propre. Crucialement, fetch ne rejette que sur les erreurs réseau, PAS sur les statuts d'erreur HTTP (404, 500). Vérifiez toujours response.ok (statut 200-299) avant l'analyse. Utilisez async/await pour un code séquentiel lisible. response.json() est async car il lit le flux du corps.
// fetch() returns a Promise; .json() returns another Promise
fetch("https://api.example.com/users")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json(); // or .text(), .blob(), .arrayBuffer()
})
.then(data => console.log(data))
.catch(error => console.error("Fetch failed:", error));
// fetch does NOT reject on HTTP errors (404, 500) — only on network
// failures. You MUST check response.ok manually.
// Same with async/await (preferred)
async function getUsers() {
try {
const res = await fetch("https://api.example.com/users");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.error(err);
}
}POST, PUT, DELETE avec fetch
L'objet d'options fetch configure la méthode, les en-têtes et le corps. Pour JSON, définissez Content-Type: application/json et JSON.stringify le corps. Pour les téléchargements de fichiers, utilisez FormData (ne définissez pas Content-Type manuellement — le navigateur ajoute la limite multipart). PUT remplace une ressource entièrement ; PATCH la met à jour partiellement.
// POST: create a resource
async function createUser(data) {
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
return res.json();
}
createUser({ name: "Alice", email: "[email protected]" });
// PUT: update a resource (full replace)
await fetch("/api/users/1", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice Updated" }),
});
// DELETE
await fetch("/api/users/1", { method: "DELETE" });
// Sending form data (file uploads)
const formData = new FormData();
formData.append("file", fileInput.files[0]);
await fetch("/upload", { method: "POST", body: formData });
// Don't set Content-Type for FormData — browser sets it with boundaryEn-têtes de requête & Auth
Les en-têtes transportent les métadonnées et les jetons d'auth. Les jetons Bearer (JWT) vont dans l'en-tête Authorization. Certains en-têtes sont 'interdits' (contrôlés par le navigateur) comme Host et Cookie. CORS est appliqué par le navigateur, pas le serveur — vous ne pouvez pas le contourner depuis le JS client ; le serveur doit envoyer Access-Control-Allow-Origin. Les requêtes OPTIONS de pré-vérification se produisent pour les requêtes non simples.
// Custom headers (e.g., Bearer token auth)
async function fetchWithAuth(url, options = {}) {
const token = localStorage.getItem("authToken");
const res = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
"Accept": "application/json",
...options.headers, // allow overrides
},
});
return res;
}
// Common headers:
// Content-Type: application/json | application/x-www-form-urlencoded
// Authorization: Bearer <token> | Basic <base64>
// Accept: application/json
// X-Requested-With: XMLHttpRequest (anti-CSRF)
// CORS: browser blocks cross-origin requests unless the server
// returns Access-Control-Allow-Origin. fetch can't bypass CORS.AbortController (Annuler des requêtes)
AbortController annule les requêtes fetch — essentiel pour la recherche-avec-saisie, la navigation ailleurs ou les délais. Passez signal à fetch ; appeler controller.abort() déclenche une AbortError. Sans cela, les requêtes périmées peuvent mettre à jour l'UI dans le désordre. AbortController fonctionne également avec d'autres APIs async et est le mécanisme d'annulation standard en JS moderne.
// AbortController lets you cancel in-flight fetch requests
const controller = new AbortController();
async function fetchWithTimeout(url, ms = 5000) {
const timeout = setTimeout(() => controller.abort(), ms);
try {
const res = await fetch(url, { signal: controller.signal });
return await res.json();
} catch (err) {
if (err.name === "AbortError") {
console.log("Request was aborted (timeout or cancel)");
} else {
throw err;
}
} finally {
clearTimeout(timeout);
}
}
// Cancel manually (e.g., user navigated away)
// controller.abort();
// Practical: cancel on new search input
searchInput.addEventListener("input", (e) => {
controller.abort(); // cancel previous request
fetchResults(e.target.value);
});Réponses en streaming
response.body est un ReadableStream — vous pouvez traiter les données par morceaux au fur et à mesure de leur arrivée au lieu de mettre en mémoire tampon toute la réponse. C'est essentiel pour les gros fichiers, les logs en streaming ou les données en temps réel. Utilisez TextDecoder pour les flux de texte. La boucle reader.read() continue jusqu'à ce que done soit true. Le streaming évite les pics de mémoire sur les grosses charges utiles.
// Read a large response in chunks (streaming)
async function streamJson(url) {
const res = await fetch(url);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Process complete lines
const lines = buffer.split("\n");
buffer = lines.pop(); // keep incomplete line
for (const line of lines) {
if (line.trim()) console.log(JSON.parse(line));
}
}
}
// Useful for: large files, NDJSON logs, real-time data feeds
// Processes data as it arrives instead of waiting for the full responseStockage Web (LocalStorage & SessionStorage)
Bases de localStorage & sessionStorage
localStorage persiste indéfiniment ; sessionStorage se vide lorsque l'onglet se ferme. Les deux ne stockent que des chaînes — utilisez JSON.stringify/parse pour les objets. Le stockage est synchrone et bloque le thread principal, évitez donc de stocker de grosses données. Disponible dans tous les navigateurs modernes mais peut être désactivé en mode navigation privée. La capacité est d'environ 5-10 Mo par origine.
// Both store strings only — JSON.stringify objects
// localStorage: persists until cleared (survives browser restart)
// sessionStorage: cleared when the tab closes
// localStorage API
localStorage.setItem("username", "Alice");
const name = localStorage.getItem("username"); // "Alice"
localStorage.removeItem("username");
localStorage.clear(); // remove ALL items
// sessionStorage API (same interface, tab-scoped)
sessionStorage.setItem("tempToken", "abc123");
// Storing objects (must serialize)
const prefs = { theme: "dark", lang: "en" };
localStorage.setItem("prefs", JSON.stringify(prefs));
const loaded = JSON.parse(localStorage.getItem("prefs"));
console.log(loaded.theme); // "dark"
// Check if storage exists (private mode may disable it)
if (typeof Storage !== "undefined") {
// localStorage/sessionStorage available
}Assistant de stockage avec TTL & JSON
Le stockage Web n'a pas d'expiration intégrée — ce wrapper ajoute un TTL (temps à vivre) en stockant un horodatage d'expiration à côté de la valeur. C'est le motif standard pour mettre en cache les réponses d'API ou les données de session qui doivent expirer. Enveloppez toujours l'accès au stockage dans try/catch en production, car JSON.parse peut lever une exception sur des données corrompues et le quota peut être dépassé.
// A robust storage wrapper with expiration (TTL)
const store = {
set(key, value, ttlMs) {
const item = {
value: value,
expiry: ttlMs ? Date.now() + ttlMs : null,
};
localStorage.setItem(key, JSON.stringify(item));
},
get(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const item = JSON.parse(raw);
if (item.expiry && Date.now() > item.expiry) {
localStorage.removeItem(key);
return null; // expired
}
return item.value;
},
remove(key) { localStorage.removeItem(key); },
};
// Usage: cache data for 5 minutes
store.set("userData", { name: "Alice" }, 5 * 60 * 1000);
console.log(store.get("userData")); // { name: "Alice" }
// After 5 min: returns null and cleans upÉvénements de stockage (Synchronisation inter-onglets)
L'événement storage est un canal de communication inter-onglets intégré — lorsqu'un onglet modifie localStorage, tous les autres onglets de la même origine reçoivent l'événement (mais pas l'onglet d'origine). Cela permet de synchroniser l'état comme login/logout, mises à jour de panier ou changements de thème entre les onglets sans WebSockets. L'événement inclut key, oldValue, newValue et url.
// The 'storage' event fires in OTHER tabs when storage changes
// (not in the tab that made the change)
window.addEventListener("storage", (event) => {
console.log("Key changed:", event.key);
console.log("Old value:", event.oldValue);
console.log("New value:", event.newValue);
console.log("URL:", event.url);
if (event.key === "cart") {
updateCartDisplay(JSON.parse(event.newValue));
}
});
// Practical: sync logout across tabs
// Tab A: localStorage.setItem("logout", Date.now());
// Tab B: receives storage event -> redirects to login
// Practical: broadcast a message to all tabs
function broadcast(type, data) {
localStorage.setItem("broadcast", JSON.stringify({ type, data, t: Date.now() }));
}
window.addEventListener("storage", (e) => {
if (e.key === "broadcast") {
const msg = JSON.parse(e.newValue);
handleMessage(msg);
}
});IndexedDB (Stockage structuré volumineux)
IndexedDB est une puissante base de données NoSQL dans le navigateur — async, transactionnelle et capable de stocker beaucoup plus de données que localStorage (des centaines de Mo). Elle prend en charge les index, les curseurs et les transactions. L'API brute est basée sur des rappels et verbeuse ; le package npm 'idb' fournit un wrapper propre basé sur des Promesses. Utilisez IndexedDB pour les apps offline-first, les gros caches ou les données complexes côté client.
// IndexedDB: async, transactional NoSQL store for large data
// Capacity: hundreds of MB to GB (far more than localStorage's 5MB)
// Open a database
const request = indexedDB.open("MyDatabase", 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
// Create an object store (like a table) with a key path
if (!db.objectStoreNames.contains("users")) {
db.createObjectStore("users", { keyPath: "id" });
}
};
request.onsuccess = (e) => {
const db = e.target.result;
// Add data in a transaction
const tx = db.transaction("users", "readwrite");
const store = tx.objectStore("users");
store.add({ id: 1, name: "Alice", age: 30 });
store.add({ id: 2, name: "Bob", age: 25 });
// Query
store.get(1).onsuccess = (e) => {
console.log(e.target.result); // { id: 1, name: "Alice", age: 30 }
};
};
// For easier use, consider the 'idb' library (Promise-based wrapper)Comparaison Cookies vs Stockage
Les cookies sont envoyés avec chaque requête HTTP (ajoutant de la bande passante) et sont la norme pour l'auth côté serveur (IDs de session, jetons CSRF). localStorage/sessionStorage sont côté client uniquement et détiennent beaucoup plus de données. IndexedDB est pour les grandes données structurées. Choisissez selon : le serveur en a-t-il besoin ? (cookie) Combien de données ? (stockage vs IndexedDB) Combien de temps ? (session vs local). Définissez Secure, HttpOnly et SameSite sur les cookies sensibles à la sécurité.
// COOKIES: sent with every HTTP request, ~4KB limit
document.cookie = "session=abc123; max-age=3600; path=/; Secure; SameSite=Strict";
console.log(document.cookie); // "session=abc123; theme=dark"
// Set cookie with attributes
document.cookie = "token=xyz; max-age=86400; Secure; HttpOnly; SameSite=Lax";
// Note: HttpOnly can't be set via JS (server-only for security)
// LOCAL STORAGE: ~5-10MB, NOT sent to server, synchronous
localStorage.setItem("theme", "dark");
// SESSION STORAGE: ~5MB, cleared on tab close
sessionStorage.setItem("draft", "work in progress");
// WHEN TO USE WHAT:
// Cookies -> auth tokens that the server needs to read, server-side sessions
// localStorage -> user preferences, cached data that persists
// sessionStorage -> temporary per-tab state (form drafts, wizard steps)
// IndexedDB -> large datasets, offline data, complex queriesMinuteurs (setTimeout, setInterval)
setTimeout & setInterval
setTimeout exécute un rappel une fois après un délai ; setInterval l'exécute de façon répétée. Les deux renvoient un ID pour l'annulation via clearTimeout/clearInterval. Les minuteurs ne sont pas précis — ce sont des délais minimums soumis à la boucle d'événement, la visibilité de l'onglet (limitée dans les onglets en arrière-plan) et la disponibilité du thread principal. Les délais sous 4 ms peuvent être limités à 4 ms dans les minuteurs imbriqués.
// setTimeout: run once after a delay (milliseconds)
const timeoutId = setTimeout(() => {
console.log("Runs after 2 seconds");
}, 2000);
// Cancel before it fires
clearTimeout(timeoutId);
// setInterval: run repeatedly every interval
const intervalId = setInterval(() => {
console.log("Runs every 1 second");
}, 1000);
// Stop the interval
clearInterval(intervalId);
// Pass arguments to the callback
setTimeout((greeting, name) => {
console.log(`${greeting}, ${name}`);
}, 1000, "Hello", "Alice");
// NOTE: timers don't guarantee exact timing — they run after the
// minimum delay, but only when the call stack is empty (event loop)setTimeout récursif (Mieux que setInterval)
setTimeout récursif est préféré à setInterval pour les tâches async récurrentes car il garantit que l'appel précédent se termine avant le suivant — pas d'exécutions chevauchantes. Il permet aussi des intervalles dynamiques (ex. backoff plus long sur erreurs). setInterval peut empiler des appels si le gestionnaire prend plus de temps que l'intervalle, causant des problèmes de performance.
// setInterval problems: doesn't wait for the previous call to finish
// If a call takes longer than the interval, they can stack up.
// Better: recursive setTimeout — guarantees the gap between completions
function poll() {
fetch("/api/status")
.then(r => r.json())
.then(data => {
console.log(data);
setTimeout(poll, 1000); // schedule next AFTER this finishes
})
.catch(err => {
console.error(err);
setTimeout(poll, 5000); // retry with backoff on error
});
}
poll(); // start the loop
// This pattern: always waits for the previous call to complete
// before scheduling the next, avoiding overlap/stacking.requestAnimationFrame (Animations fluides)
requestAnimationFrame (rAF) est la bonne façon de faire des animations visuelles — il se synchronise avec le cycle de repeinture du navigateur (~60fps), évite les images inutiles lorsque l'onglet est caché et produit des animations plus fluides que setInterval. Utilisez le paramètre timestamp pour les calculs de delta-temps afin de garder la vitesse d'animation cohérente entre différents taux de rafraîchissement. Annulez toujours avec cancelAnimationFrame lorsque terminé.
// requestAnimationFrame: syncs with the display refresh (~60fps)
// More efficient than setInterval for visual animations
function animate() {
element.style.left = parseInt(element.style.left || 0) + 2 + "px";
if (parseInt(element.style.left) < 300) {
requestAnimationFrame(animate); // schedule next frame
}
}
requestAnimationFrame(animate);
// Cancel if needed
const rafId = requestAnimationFrame(animate);
cancelAnimationFrame(rafId);
// Timestamp for delta-time calculations
let lastTime = 0;
function loop(timestamp) {
const delta = timestamp - lastTime;
lastTime = timestamp;
update(delta); // frame-rate independent movement
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
// rAF pauses in background tabs (saves CPU/battery)Debounce & Throttle
Debounce attend une pause dans les appels avant d'exécuter (ex. rechercher 300 ms seulement après que l'utilisateur arrête de taper). Throttle limite l'exécution à une fois par intervalle (ex. mettre à jour la position de défilement au plus toutes les 200 ms). Les deux empêchent les problèmes de performance des événements à haute fréquence. Debounce = 'grouper les appels rapides en un' ; throttle = 'plafonner le taux d'appels'.
// Debounce: delay execution until calls stop for N ms
// Use for: search input, window resize, button spam
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer); // reset the countdown
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const search = debounce((query) => {
fetchResults(query);
}, 300);
input.addEventListener("input", (e) => search(e.target.value));
// Throttle: execute at most once per N ms
// Use for: scroll, mousemove, drag handlers
function throttle(fn, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
const onScroll = throttle(() => {
console.log("Scroll position:", window.scrollY);
}, 200);
window.addEventListener("scroll", onScroll);setTimeout promisifié (delay)
Envelopper setTimeout dans une Promesse crée une fonction 'delay' propre pour async/await — bien plus lisible que les chaînes de rappels. Ce motif alimente la logique de réessai avec backoff exponentiel (attendre 1s, 2s, 4s entre les essais), les animations séquentielles et la limitation de débit. L'assistant delay est l'un des plus utiles petits utilitaires du JS async moderne.
// A promise-based delay for use with async/await
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function countdown() {
console.log("3...");
await delay(1000);
console.log("2...");
await delay(1000);
console.log("1...");
await delay(1000);
console.log("Go!");
}
countdown();
// Retry with exponential backoff
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (res.ok) return await res.json();
throw new Error(`HTTP ${res.status}`);
} catch (err) {
if (i === retries - 1) throw err;
const wait = Math.pow(2, i) * 1000; // 1s, 2s, 4s
console.log(`Retry ${i + 1} in ${wait}ms`);
await delay(wait);
}
}
}Map, Set & WeakMap
Map (Clé-Valeur avec tout type de clé)
Map est une collection clé-valeur propre où les clés peuvent être de tout type (objets, fonctions, nombres) — contrairement aux Objets qui forcent les clés en chaînes. Map préserve l'ordre d'insertion, a une propriété .size et est directement itérable. Utilisez Map lorsque vous avez besoin de clés non-chaînes, d'ajouts/suppressions fréquents, ou lorsque la collection n'est pas un enregistrement/DTO. Utilisez Object pour les données à forme fixe.
// Map: like an Object but keys can be ANY type (not just strings)
const map = new Map();
// Keys can be objects, functions, primitives
const objKey = { id: 1 };
map.set("name", "Alice");
map.set(objKey, "data for this object");
map.set(42, "numeric key");
map.set(true, "boolean key");
console.log(map.get("name")); // "Alice"
console.log(map.get(objKey)); // "data for this object"
console.log(map.size); // 4
console.log(map.has(42)); // true
// Iteration preserves insertion order
for (const [key, value] of map) {
console.log(key, value);
}
// Map vs Object:
// - Map keys can be any type; Object keys are strings/symbols
// - Map has .size; Object needs Object.keys().length
// - Map is iterable by default; Object needs Object.entries()
// - Map performs better for frequent add/removeSet (Valeurs uniques)
Set stocke des valeurs uniques — parfait pour la déduplication et les tests d'appartenance. Convertir un tableau en Set et inversement ([...new Set(arr)]) est la méthode idiomatique pour supprimer les doublons. Set.has() est O(1) contre Array.includes() qui est O(n), utilisez donc Set pour les grandes collections que vous vérifiez fréquemment. Set n'a pas map/filter — déployez en tableau d'abord.
// Set: collection of unique values (no duplicates)
const set = new Set([1, 2, 3, 2, 1]);
console.log(set); // Set(3) { 1, 2, 3 }
console.log(set.size); // 3
set.add(4);
set.add(4); // ignored (already exists)
console.log(set.has(3)); // true
set.delete(2);
// Iterate (preserves insertion order)
for (const item of set) {
console.log(item);
}
// Common use cases:
// 1. Remove duplicates from an array
const unique = [...new Set([1, 2, 2, 3, 3, 3])]; // [1, 2, 3]
// 2. Check for membership (faster than Array.includes for large sets)
const validIds = new Set([101, 102, 103]);
if (validIds.has(userId)) { /* ... */ }
// 3. Set operations (union, intersection)
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // {1,2,3,4}
const intersect = new Set([...a].filter(x => b.has(x))); // {2,3}WeakMap & WeakSet (Références sûres pour la mémoire)
WeakMap et WeakSet détiennent des références faibles aux objets — lorsqu'aucune autre référence n'existe, l'entrée est collectée par le GC automatiquement. Cela empêche les fuites de mémoire lors de l'association de données à des éléments DOM ou d'autres objets. Les clés doivent être des objets. WeakMap/WeakSet ne sont pas itérables et n'ont pas de .size car les entrées peuvent disparaître à tout moment pendant le GC. Utilisez-les pour le cache/métadonnées liés à la durée de vie des objets.
// WeakMap: keys must be objects; references are "weak"
// (doesn't prevent garbage collection of the key object)
const weakMap = new WeakMap();
let user = { name: "Alice" };
weakMap.set(user, "metadata");
console.log(weakMap.get(user)); // "metadata"
user = null; // remove the only strong reference
// Now the object can be GC'd, and WeakMap entry disappears automatically
// Practical: attach data to DOM elements without memory leaks
const elementData = new WeakMap();
function cacheData(element, data) {
elementData.set(element, data);
}
// When the DOM element is removed, its data is auto-cleaned
// WeakSet: collection of objects, weakly held
const processed = new WeakSet();
function processOnce(obj) {
if (processed.has(obj)) return; // already done
processed.add(obj);
doWork(obj);
}
// WeakMap/WeakSet are NOT iterable (no .size, no for...of)Itération de Map & Conversion
Les Maps sont itérables dans l'ordre d'insertion via for...of (entries par défaut), .keys(), .values() ou .forEach(). Convertissez entre Maps et Objets en utilisant Object.entries() et Object.fromEntries(). Cette conversion bidirectionnelle est pratique lorsque les APIs attendent des objets simples mais que vous voulez les fonctionnalités de Map en interne. Rappelez-vous : les clés d'objet deviennent de type chaîne dans la conversion.
const map = new Map([
["name", "Alice"],
["age", 30],
["city", "NYC"],
]);
// Iterate entries (default)
for (const [key, value] of map) {
console.log(`${key} = ${value}`);
}
// Iterate keys only
for (const key of map.keys()) {
console.log(key);
}
// Iterate values only
for (const value of map.values()) {
console.log(value);
}
// forEach (callback style)
map.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// Convert to Array
const entries = [...map.entries()]; // [["name","Alice"], ["age",30], ...]
const keys = [...map.keys()]; // ["name", "age", "city"]
const values = [...map.values()]; // ["Alice", 30, "NYC"]
// Convert Object to Map and back
const obj = { a: 1, b: 2 };
const mapFromObj = new Map(Object.entries(obj));
const objFromMap = Object.fromEntries(map);Choisir entre Map, Set, Object & Array
Choisissez la bonne collection : Arrays pour les listes ordonnées avec doublons et accès par index ; Objects pour les enregistrements à forme fixe et JSON ; Maps pour les collections clé-valeur dynamiques avec tout type de clé ; Sets pour l'unicité et la recherche rapide. Utiliser la mauvaise structure mène à du code verbeux et des problèmes de performance — ex. vérifier Array.includes() dans une boucle vs Set.has().
// Decision guide:
// Use ARRAY when:
// - You need ordered data with duplicates
// - You need index-based access (arr[5])
// - You'll map/filter/reduce frequently
const todoList = ["buy milk", "walk dog"];
// Use OBJECT when:
// - You have a fixed known shape (like a user record)
// - You need JSON serialization (JSON.stringify)
// - Keys are strings and known at write time
const user = { name: "Alice", age: 30 };
// Use MAP when:
// - Keys are not strings (objects, functions, numbers)
// - You frequently add/remove key-value pairs
// - You need to iterate in insertion order
// - The collection size changes dynamically
const handlers = new Map();
handlers.set(buttonElement, () => onClick());
// Use SET when:
// - You need unique values (no duplicates)
// - You need fast membership testing (.has is O(1))
// - Order doesn't matter much
const seen = new Set();
if (!seen.has(url)) { seen.add(url); visit(url); }Générateurs & Itérateurs
Itérateurs & Symbol.iterator
Un itérateur a une méthode next() renvoyant {value, done}. Un itérable implémente Symbol.iterator, qui renvoie un itérateur. Les itérables intégrés (tableaux, chaînes, Maps, Sets) fonctionnent avec for...of, le spread (...), la déstructuration et Array.from(). Implémenter Symbol.iterator laisse vos objets personnalisés fonctionner avec toutes ces fonctionnalités du langage de manière transparente.
// An iterator is an object with a next() method returning {value, done}
function makeIterator(arr) {
let i = 0;
return {
next() {
return i < arr.length
? { value: arr[i++], done: false }
: { value: undefined, done: true };
},
};
}
const it = makeIterator(["a", "b", "c"]);
console.log(it.next()); // { value: "a", done: false }
console.log(it.next()); // { value: "b", done: false }
console.log(it.next()); // { value: "c", done: false }
console.log(it.next()); // { value: undefined, done: true }
// An iterable implements Symbol.iterator (returns an iterator)
const range = {
from: 1, to: 3,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
},
};
},
};
// Now range works with for...of, spread, etc.
console.log([...range]); // [1, 2, 3]Fonctions génératrices (function*)
Les fonctions génératrices (function*) utilisent yield pour suspendre et reprendre l'exécution — elles produisent des valeurs paresseusement, une à la fois. Cela permet les séquences infinies, l'évaluation paresseuse et les pipelines efficaces en mémoire. Les générateurs sont à la fois des itérateurs et des itérables. Chaque appel à next() s'exécute jusqu'au yield suivant (ou return). Ils sont la fondation des générateurs async et des motifs de coroutine JS.
// Generators: pause execution with 'yield', resume with next()
function* idGenerator() {
let id = 1;
while (true) {
yield id++; // pause here, return value, resume on next()
}
}
const gen = idGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
// Infinite sequence — only computes values on demand
// Generators are iterables (work with for...of)
function* take(n, iterable) {
for (const item of iterable) {
if (n-- <= 0) return;
yield item;
}
}
function* naturalNumbers() {
let n = 1;
while (true) yield n++;
}
const firstFive = [...take(5, naturalNumbers())];
console.log(firstFive); // [1, 2, 3, 4, 5]yield* (Déléguer à un autre générateur)
yield* délègue tous les yields à un autre itérable ou générateur — aplatissant les structures imbriquées et composant les générateurs proprement. C'est l'équivalent JS du 'yield from' de Python. Les valeurs du générateur délégué sont yields une par une comme si elles faisaient partie du générateur externe. C'est la méthode standard pour composer des générateurs récursivement.
// yield* delegates to another iterable/generator
function* inner() {
yield "a";
yield "b";
}
function* outer() {
yield 1;
yield* inner(); // delegates: yields "a", then "b"
yield* [10, 20]; // also works with arrays
yield 2;
}
console.log([...outer()]); // [1, "a", "b", 10, 20, 2]
// Practical: flatten nested structures
function* flatten(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flatten(item); // recursive delegation
} else {
yield item;
}
}
}
const nested = [1, [2, [3, 4]], 5];
console.log([...flatten(nested)]); // [1, 2, 3, 4, 5]send() de générateur via next(value)
Les générateurs prennent en charge la communication bidirectionnelle : next(value) passe une valeur (devient le résultat de la dernière expression yield), et throw(error) injecte une exception au point de yield. Cela permet les coroutines, les machines à états et les motifs async basés sur des générateurs qui alimentaient les premières implémentations async/await. Le premier appel next() ne peut pas passer de valeur (rien pour la recevoir encore).
// next(value) passes a value INTO the generator (becomes yield's result)
function* conversation() {
const name = yield "What's your name?"; // receives via next()
const age = yield `Hello ${name}, how old are you?`;
return `${name} is ${age} years old`;
}
const talk = conversation();
console.log(talk.next().value); // "What's your name?"
console.log(talk.next("Alice").value); // "Hello Alice, how old are you?"
console.log(talk.next(30).value); // "Alice is 30 years old"
// throw(error) injects an exception at the yield point
function* safeGen() {
try {
yield "step 1";
} catch (e) {
console.log("Caught:", e.message);
yield "recovered";
}
}
const g = safeGen();
g.next(); // "step 1"
g.throw(new Error("oops")); // "Caught: oops" -> "recovered"Générateurs async
Les générateurs async (async function*) combinent les générateurs avec async/await — chaque yield peut produire une valeur après un await. Consommez-les avec for await...of. C'est idéal pour les APIs paginées, les données en streaming ou tout scénario où vous produisez des valeurs de manière asynchrone. C'est la méthode moderne pour gérer les flux de données en JS sans rappels ou chaînage manuel de promesses.
// async function* : yields promises, can use await
async function* fetchPages(url) {
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}`);
const data = await res.json();
if (data.items.length === 0) break; // no more pages
yield data.items;
page++;
}
}
// Consume with for await...of
(async () => {
for await (const items of fetchPages("/api/products")) {
console.log(`Got ${items.length} items`);
renderItems(items);
}
console.log("All pages loaded");
})();
// Practical: stream data as it arrives
async function* streamLines(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) yield line;
}
}Proxy & Reflect
Bases de Proxy (Intercepter des opérations)
Proxy vous permet d'intercepter et personnaliser les opérations fondamentales sur un objet — get, set, has, deleteProperty, ownKeys et plus. L'objet handler définit des 'pièges' (comme des getters/setters mais pour toutes les propriétés). Cas d'usage : validation, journalisation, valeurs par défaut, contrôle d'accès, systèmes réactifs (Vue 3 utilise Proxy pour la réactivité). Renvoyez true depuis set pour indiquer le succès en mode strict.
// Proxy wraps an object and intercepts fundamental operations
const target = { name: "Alice", age: 30 };
const handler = {
get(obj, prop) {
console.log(`Reading ${prop}`);
return prop in obj ? obj[prop] : "N/A"; // default value
},
set(obj, prop, value) {
console.log(`Setting ${prop} = ${value}`);
if (prop === "age" && value < 0) return false; // validation
obj[prop] = value;
return true; // indicate success
},
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // logs "Reading name", returns "Alice"
proxy.age = 25; // logs "Setting age = 25"
// proxy.age = -5; // logs "Setting age = -5", rejected
console.log(proxy.unknown); // logs "Reading unknown", returns "N/A"Cas d'usage courants de Proxy
Les Proxies permettent une métaprogrammation puissante : indices négatifs, calcul paresseux de propriétés, vues en lecture seule, validation, journalisation et liaison de données réactive. Le système de réactivité de Vue 3 utilise Proxy pour suivre l'accès aux propriétés et déclencher des re-rendus automatiquement. Le compromis est un petit coût de performance, utilisez donc les Proxies là où l'abstraction en vaut la peine, pas pour chaque objet.
// 1. Negative array indices (like Python)
function negativeArray(arr) {
return new Proxy(arr, {
get(target, prop) {
const idx = Number(prop);
if (Number.isInteger(idx)) {
return target[idx < 0 ? target.length + idx : idx];
}
return target[prop];
},
});
}
const arr = negativeArray(["a", "b", "c"]);
console.log(arr[-1]); // "c"
// 2. Auto-populating / lazy properties
const lazy = new Proxy({}, {
get(target, prop) {
if (!(prop in target)) {
target[prop] = expensiveCompute(prop); // compute on first access
}
return target[prop];
},
});
// 3. Read-only object
const readOnly = new Proxy(data, {
set() { throw new Error("This object is read-only"); },
deleteProperty() { throw new Error("Cannot delete"); },
});API Reflect
Reflect fournit les mêmes opérations que les pièges Proxy interceptent — utilisez-le dans les pièges pour transférer au comportement par défaut proprement. Les méthodes Reflect renvoient des booléens (succès/échec) au lieu de lever des exceptions, les rendant plus sûres pour la logique conditionnelle. Reflect.ownKeys renvoie toutes les clés (chaînes ET symboles), contrairement à Object.keys qui ne renvoie que les clés de chaînes énumérables. Ensemble, Proxy et Reflect forment la boîte à outils de métaprogrammation de JS.
// Reflect provides default behavior for proxy traps and
// functional equivalents of object operations
const obj = { x: 1, y: 2 };
// Reflect.get / Reflect.set (instead of obj[prop])
console.log(Reflect.get(obj, "x")); // 1
Reflect.set(obj, "z", 3); // obj.z = 3
console.log(Reflect.has(obj, "x")); // true (like "x" in obj)
Reflect.deleteProperty(obj, "y"); // delete obj.y
console.log(Reflect.ownKeys(obj)); // ["x", "z"]
// In a Proxy, use Reflect to forward to the default behavior
const proxy = new Proxy({}, {
get(target, prop, receiver) {
console.log(`get ${prop}`);
return Reflect.get(target, prop, receiver); // default behavior
},
set(target, prop, value, receiver) {
console.log(`set ${prop}`);
return Reflect.set(target, prop, value, receiver);
},
});
// Reflect.construct: call a constructor with an array of args
const instance = Reflect.construct(Array, [1, 2, 3]);Objet réactif avec Proxy (style Vue)
C'est l'idée centrale derrière la réactivité de Vue 3 — un Proxy intercepte get (pour suivre quels effets dépendent d'une propriété) et set (pour déclencher ces effets lorsque la propriété change). Cela permet des mises à jour d'UI déclaratives : vous mutez l'état, et le framework se re-rend automatiquement. Les Proxies ont rendu cela possible en ES6 ; les frameworks antérieurs (Vue 2) utilisaient Object.defineProperty avec des limitations.
// A minimal reactive system using Proxy
function reactive(target) {
const subscribers = new Set();
const handler = {
get(obj, prop) {
track(prop); // record who reads this property
const value = Reflect.get(obj, prop);
return typeof value === "object" && value !== null
? reactive(value) // deeply reactive
: value;
},
set(obj, prop, value) {
const result = Reflect.set(obj, prop, value);
trigger(prop); // notify subscribers
return result;
},
};
let currentEffect = null;
function track(prop) {
if (currentEffect) subscribers.add(currentEffect);
}
function trigger(prop) {
subscribers.forEach(fn => fn());
}
return new Proxy(target, handler);
}
const state = reactive({ count: 0 });
// When state.count changes, effects re-run automaticallyWeb Workers
Créer un Web Worker
Les Web Workers exécutent JavaScript dans un thread d'arrière-plan séparé, permettant un véritable parallélisme sans bloquer l'UI. Le thread principal et le worker communiquent via postMessage (les données sont copiées/clonées en structure, pas partagées). Les workers ne peuvent pas accéder au DOM ou à l'objet window — ils sont isolés. Utilisez les workers pour les tâches intensives en CPU comme le traitement d'images, l'analyse de gros fichiers ou les calculs complexes.
// main.js — workers run JS in a background thread
const worker = new Worker("worker.js");
// Send data to the worker
worker.postMessage({ command: "calculate", data: [1, 2, 3, 4, 5] });
// Receive results from the worker
worker.onmessage = (event) => {
console.log("Result from worker:", event.data);
};
worker.onerror = (error) => {
console.error("Worker error:", error.message);
};
// Terminate when done (frees resources)
// worker.terminate();
// --- worker.js (separate file) ---
// self.onmessage = (event) => {
// const { command, data } = event.data;
// const result = heavyComputation(data);
// self.postMessage(result);
// };Workers en ligne (Blob URL)
Les workers en ligne créent un Worker à partir d'une chaîne de code via une Blob URL — pas de fichier séparé nécessaire. C'est pratique pour les démos, petits utilitaires, ou lorsque votre système de build ne gère pas facilement les fichiers de worker séparés. N'oubliez pas de révoquer l'URL de l'objet pour éviter les fuites de mémoire. Le code du worker est une chaîne, donc vous perdez la coloration syntaxique de l'éditeur et la vérification de type — utilisez avec soin en production.
// Create a worker from a string (no separate file needed)
const workerCode = `
self.onmessage = function(e) {
const result = e.data.map(x => x * x);
self.postMessage(result);
};
`;
const blob = new Blob([workerCode], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
worker.postMessage([1, 2, 3, 4]);
worker.onmessage = (e) => console.log(e.data); // [1, 4, 9, 16]
// Clean up the blob URL when done
// URL.revokeObjectURL(blob URL);
// Useful for: single-file demos, bundlers that inline workers,
// or when you can't serve a separate .js fileObjets transférables (Copie nulle)
Normalement, postMessage copie les données (clonage structuré), ce qui est lent pour les gros tampons. La liste de transfert (deuxième argument) DÉPLACE la propriété de ArrayBuffer/MessagePort/ImageBitmap vers le worker avec copie nulle — quasi-instantané quelle que soit la taille. Le tampon original devient détaché (inutilisable). Utilisez cela pour les gros jeux de données, le traitement d'images ou l'audio pour éviter le coût de copie.
// Transferable objects move data to a worker WITHOUT copying
// (the original becomes unusable) — much faster for large ArrayBuffers
// Create a large buffer
const buffer = new ArrayBuffer(1024 * 1024 * 10); // 10MB
const view = new Float64Array(buffer);
view[0] = 3.14;
// Transfer the buffer (second arg = transfer list)
worker.postMessage({ data: buffer }, [buffer]);
// After transfer, 'buffer' is detached (length becomes 0)
console.log(buffer.byteLength); // 0 — ownership moved to worker
// Worker receives it normally:
// self.onmessage = (e) => {
// const buf = e.data.data; // full 10MB, no copy
// };
// Transferable types: ArrayBuffer, MessagePort, ImageBitmap
// Use for: large datasets, image data, audio buffersSharedArrayBuffer & Atomics
SharedArrayBuffer permet une véritable mémoire partagée entre threads (pas de copie), et Atomics fournit des opérations thread-safe (add, load, store, compareExchange, wait/notify) dessus. Cela permet des algorithmes parallèles haute performance en JS. En raison des préoccupations de sécurité Spectre, SharedArrayBuffer nécessite des en-têtes HTTP d'isolation cross-origin — sans eux, il est désactivé dans les navigateurs modernes. Utilisez pour l'interop WASM et les calculs parallèles lourds.
// SharedArrayBuffer: memory shared between main thread and workers
// (both can read/write simultaneously) — true shared memory
// const sharedBuffer = new SharedArrayBuffer(1024);
// const view = new Int32Array(sharedBuffer);
// worker.postMessage({ buffer: sharedBuffer });
// Atomics: thread-safe operations on SharedArrayBuffer
// const sharedArray = new Int32Array(sharedBuffer);
// Atomics.add(sharedArray, 0, 1); // atomic increment
// Atomics.load(sharedArray, 0); // atomic read
// Atomics.store(sharedArray, 0, 42); // atomic write
// Atomics.compareExchange(sharedArray, 0, 42, 99); // CAS
// Atomics.wait / notify: block and wake threads
// Worker:
// Atomics.wait(sharedArray, 0, 0); // block until index 0 != 0
// Main:
// Atomics.store(sharedArray, 0, 1);
// Atomics.notify(sharedArray, 0); // wake waiting workers
// NOTE: Requires cross-origin isolation headers:
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corpPatron de Pool de Workers
Créer des workers a un coût, donc un pool de workers réutilise un nombre fixe de workers et met en file les tâches — comme un pool de threads dans d'autres langages. Ce motif maximise l'utilisation CPU (un worker par cœur) tout en évitant le coût de création de workers par tâche. Le pool distribue les tâches aux workers libres et les met en file lorsque tous sont occupés. Essentiel pour traiter de nombreux morceaux de travail indépendants.
// A worker pool reuses workers to avoid creation overhead
class WorkerPool {
constructor(workerUrl, size = 4) {
this.workers = [];
this.queue = [];
for (let i = 0; i < size; i++) {
const worker = new Worker(workerUrl);
worker.busy = false;
this.workers.push(worker);
}
}
run(data) {
return new Promise((resolve, reject) => {
const task = { data, resolve, reject };
const worker = this.workers.find(w => !w.busy);
if (worker) {
this.execute(worker, task);
} else {
this.queue.push(task); // wait for a free worker
}
});
}
execute(worker, task) {
worker.busy = true;
worker.onmessage = (e) => {
worker.busy = false;
task.resolve(e.data);
this.next();
};
worker.postMessage(task.data);
}
next() {
const task = this.queue.shift();
const worker = this.workers.find(w => !w.busy);
if (task && worker) this.execute(worker, task);
}
}Manipulation du DOM approfondie
querySelector
querySelector renvoie la première correspondance, querySelectorAll renvoie une NodeList statique. getElementsByClassName renvoie une HTMLCollection live. NodeList prend en charge forEach ; HTMLCollection non.
const el = document.querySelector('.my-class');
const all = document.querySelectorAll('div.item');
all.forEach(el => console.log(el.textContent));
const live = document.getElementsByClassName('item'); // live
const static = document.querySelectorAll('.item'); // staticCréer & Insérer
createElement crée un nouvel élément. appendChild ajoute comme dernier enfant, prepend ajoute comme premier. textContent est plus sûr qu'innerHTML car il empêche XSS.
const div = document.createElement('div');
div.className = 'card';
div.textContent = 'Hello';
div.setAttribute('data-id', '42');
const parent = document.querySelector('#container');
parent.appendChild(div);
parent.prepend(div);Délégation d'événement
La délégation d'événement attache un écouteur à un parent au lieu de plusieurs aux enfants. closest trouve l'ancêtre le plus proche correspondant à un sélecteur. Gère les éléments ajoutés dynamiquement.
document.querySelector('#list').addEventListener('click', (e) => {
const item = e.target.closest('.list-item');
if (!item) return;
console.log('Clicked:', item.dataset.id);
});API classList
classList fournit des méthodes pour manipuler les classes CSS en toute sécurité. toggle renvoie true si ajouté, false si supprimé. Plus propre que la manipulation de chaîne className.
const el = document.querySelector('.box');
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('dark-mode');
el.classList.replace('old', 'new');
if (el.classList.contains('active')) { /* ... */ }Attributs Dataset
Les attributs data-* stockent des données personnalisées. dataset fournit un accès camelCase : data-user-id devient dataset.userId. Les valeurs sont toujours des chaînes.
// HTML: <div data-user-id="42" data-role="admin"></div>
const el = document.querySelector('[data-user-id]');
console.log(el.dataset.userId); // "42"
console.log(el.dataset.role); // "admin"
el.dataset.userId = '99';API Canvas
Dessin de base
getContext("2d") renvoie le contexte de rendu 2D. fillRect dessine un rectangle rempli, strokeRect dessine un contour. Définissez fillStyle/strokeStyle avant de dessiner.
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 50);
ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.strokeRect(130, 10, 100, 50);Chemins & Lignes
moveTo positionne le curseur sans dessiner. lineTo dessine une ligne. closePath reconnecte au départ. arc dessine des cercles/arcs.
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.lineTo(100, 150);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 35, 25, 0, Math.PI * 2);
ctx.fill();Texte & Dégradés
createLinearGradient crée un dégradé. addColorStop définit les couleurs à des positions (0-1). Définissez font et textAlign avant de dessiner du texte.
const grad = ctx.createLinearGradient(0, 0, 200, 0);
grad.addColorStop(0, 'red');
grad.addColorStop(1, 'blue');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 200, 100);
ctx.font = '24px Arial';
ctx.fillText('Hello Canvas', 100, 50);Boucle d'animation
requestAnimationFrame se synchronise avec le rafraîchissement d'affichage (~60fps). clearRect efface avant chaque image. Annulez avec cancelAnimationFrame.
let x = 0;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'green';
ctx.fillRect(x, 50, 30, 30);
x += 2;
if (x > canvas.width) x = 0;
requestAnimationFrame(animate);
}
animate();Manipulation d'image
drawImage rend les images. getImageData renvoie les données de pixels sous forme de tableaux RGBA. Manipulez les pixels pour les filtres. putImageData réécrit les pixels modifiés.
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, 200, 150);
const data = ctx.getImageData(0, 0, 200, 150);
for (let i = 0; i < data.data.length; i += 4)
data.data[i] = 255 - data.data[i];
ctx.putImageData(data, 0, 0);
};
img.src = 'photo.jpg';WebSockets
WebSocket de base
WebSocket fournit une communication full-duplex sur une seule connexion TCP. onopen se déclenche lors de la connexion, onmessage lorsque les données arrivent. Gérez toujours onerror.
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => { ws.send('Hello Server'); };
ws.onmessage = (e) => { console.log('Received:', e.data); };
ws.onclose = () => console.log('Disconnected');
ws.onerror = (err) => console.error('Error:', err);Envoyer & Recevoir du JSON
Les données WebSocket sont transmises comme chaînes ou binaires. JSON.stringify/parse permet l'échange de données structurées. Un champ type active le routage des messages.
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'message', text: 'Hello' }));
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
switch (data.type) {
case 'message': console.log(data.text); break;
}
};Reconnexion
Les connexions WebSocket peuvent tomber. Le backoff exponentiel (2^retries) empêche de submerger le serveur. Plafonnez le délai à 30s. Réinitialisez le compteur de réessais en cas de succès.
class ReconnectingWS {
constructor(url) { this.url = url; this.retries = 0; this.connect(); }
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => { this.retries = 0; };
this.ws.onclose = () => {
const delay = Math.min(1000 * 2 ** this.retries++, 30000);
setTimeout(() => this.connect(), delay);
};
}
}Données binaires
WebSocket prend en charge les données binaires via ArrayBuffer. Définissez binaryType sur arraybuffer. DataView fournit un accès typé. Le binaire est plus efficace pour les données numériques.
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt32(0, 42);
ws.send(buffer);
ws.binaryType = 'arraybuffer';
ws.onmessage = (e) => {
if (e.data instanceof ArrayBuffer) {
const v = new DataView(e.data);
console.log(v.getInt32(0));
}
};Battement de cœur
Les battements de cœur détectent les connexions périmées. Envoyez des pings et attendez des pongs. Si pas de pong dans le délai, fermez et reconnectez. readyState vérifie le statut de connexion.
setInterval(() => {
if (ws.readyState === WebSocket.OPEN)
ws.send(JSON.stringify({ type: 'ping' }));
}, 30000);
setInterval(() => {
if (Date.now() - lastPong > 60000) ws.close();
}, 10000);Service Workers
Enregistrement
Les service workers s'exécutent dans un thread séparé, interceptant les requêtes réseau. L'enregistrement doit se faire sur HTTPS ou localhost. L'emplacement du fichier SW détermine la portée.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Registered:', reg.scope))
.catch(err => console.error('Failed:', err));
}Mise en cache
Stratégie cache-first : sert depuis le cache, repli sur le réseau. install pré-cache les assets. fetch intercepte les requêtes. Autres stratégies : network-first, stale-while-revalidate.
const CACHE = 'app-v1';
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(CACHE)
.then(c => c.addAll(['/', '/index.html', '/style.css'])));
});
self.addEventListener('fetch', (e) => {
e.respondWith(caches.match(e.request)
.then(cached => cached || fetch(e.request)));
});Synchronisation en arrière-plan
La synchronisation en arrière-plan diffère les actions jusqu'au retour de la connectivité. L'événement sync se déclenche lorsque le réseau est disponible. Stockez les actions en attente dans IndexedDB.
navigator.serviceWorker.ready.then(reg =>
reg.sync.register('send-messages'));
self.addEventListener('sync', (e) => {
if (e.tag === 'send-messages')
e.waitUntil(sendPendingMessages());
});Notifications Push
Les notifications push fonctionnent même lorsque l'app est fermée. subscribe s'enregistre auprès d'un service push en utilisant les clés VAPID. userVisibleOnly exige l'affichage d'une notification.
const reg = await navigator.serviceWorker.ready;
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey
});
self.addEventListener('push', (e) => {
const data = e.data.json();
e.waitUntil(self.registration.showNotification(data.title, {
body: data.body
}));
});Mise à jour & Activation
skipWaiting active un nouveau SW immédiatement. activate nettoie les anciens caches. clients.claim prend le contrôle immédiatement. Versionnez le nom du cache pour déclencher les mises à jour.
self.addEventListener('install', (e) => {
self.skipWaiting();
});
self.addEventListener('activate', (e) => {
e.waitUntil(caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE)
.map(k => caches.delete(k)))));
self.clients.claim();
});IndexedDB
Ouvrir une base de données
IndexedDB est une base de données NoSQL dans le navigateur. onupgradeneeded se déclenche lorsque la version change, utilisé pour créer des object stores. keyPath définit la clé primaire.
const req = indexedDB.open('MyDatabase', 1);
req.onupgradeneeded = (e) => {
const db = e.target.result;
if (!db.objectStoreNames.contains('users'))
db.createObjectStore('users', { keyPath: 'id' });
};
req.onsuccess = (e) => { const db = e.target.result; };Ajouter & Put
Les transactions regroupent les opérations atomiquement. add échoue sur les clés dupliquées, put remplace. readwrite permet les modifications. oncomplete se déclenche en cas de succès.
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.add({ id: 1, name: 'Alice' }); // Fails if key exists
store.put({ id: 1, name: 'Bob' }); // Overwrites
tx.oncomplete = () => console.log('Saved');Interroger des données
get récupère par clé. openCursor itère les enregistrements. cursor.continue passe au suivant. Enveloppez dans des Promesses pour l'usage async/await.
const tx = db.transaction('users', 'readonly');
const req = tx.objectStore('users').get(1);
req.onsuccess = () => console.log(req.result);
const cursorReq = tx.objectStore('users').openCursor();
cursorReq.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) { console.log(cursor.value); cursor.continue(); }
};Requêtes d'index
Les index permettent les requêtes sur des champs non-clés. IDBKeyRange crée des bornes. Les index doivent être créés dans onupgradeneeded.
const tx = db.transaction('users', 'readonly');
const index = tx.objectStore('users').index('name');
index.get('Alice').onsuccess = (e) =>
console.log(e.target.result);
const range = IDBKeyRange.bound('A', 'M');
index.openCursor(range).onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) { cursor.continue(); }
};Supprimer & Vider
delete supprime un seul enregistrement. clear supprime tous les enregistrements. deleteDatabase supprime toute la base de données. Toutes les modifications nécessitent des transactions readwrite.
const tx = db.transaction('users', 'readwrite');
tx.objectStore('users').delete(1); // Delete by key
tx.objectStore('users').clear(); // Clear all
indexedDB.deleteDatabase('MyDatabase'); // Delete DB
tx.oncomplete = () => console.log('Done');WebRTC
Obtenir le média utilisateur
getUserMedia demande l'accès à la caméra et au microphone. Renvoie un MediaStream. srcObject affecte le flux à l'élément vidéo. Nécessite HTTPS et permission utilisateur.
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
document.querySelector('video').srcObject = stream;
});Connexion pair
RTCPeerConnection établit des connexions P2P. addTrack ajoute du média. ontrack reçoit le flux distant. Les candidats ICE sont des chemins réseau échangés via signalisation.
const pc = new RTCPeerConnection(config);
stream.getTracks().forEach(t => pc.addTrack(t, stream));
pc.ontrack = (e) => { remoteVideo.srcObject = e.streams[0]; };
pc.onicecandidate = (e) => {
if (e.candidate) sendToPeer(e.candidate);
};Offre & Réponse
Négociation SDP : l'échange offre/réponse décrit les formats média. setLocalDescription définit le SDP local, setRemoteDescription définit le SDP distant.
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
sendToPeer(offer);
// Callee:
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
sendToPeer(answer);Canaux de données
Les canaux de données permettent le transfert de données arbitraire sur WebRTC avec faible latence. createDataChannel crée côté offreur. ondatachannel reçoit côté répondeur.
const channel = pc.createDataChannel('chat');
channel.onopen = () => console.log('Connected');
channel.onmessage = (e) => console.log('Received:', e.data);
channel.send('Hello peer!');
pc.ondatachannel = (e) => {
e.channel.onmessage = (ev) => console.log(ev.data);
};Partage d'écran
getDisplayMedia capture l'écran, la fenêtre ou l'onglet. Le navigateur affiche un sélecteur. onended se déclenche lorsque l'utilisateur arrête le partage. Gérez toujours le nettoyage.
const stream = await navigator.mediaDevices.getDisplayMedia({
video: { frameRate: 30 }, audio: true
});
video.srcObject = stream;
stream.getVideoTracks()[0].onended = () =>
console.log('Stopped');Optimisation des performances
Debounce & Throttle
Debounce retarde l'exécution jusqu'à l'arrêt des appels (bon pour la recherche). Throttle limite à une fois par intervalle (bon pour le défilement). Les deux empêchent les appels excessifs.
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
function throttle(fn, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) { fn(...args); inThrottle = true;
setTimeout(() => inThrottle = false, limit); }
};
}Web Workers
Les Web Workers exécutent JavaScript dans un thread séparé pour les tâches intensives en CPU. Les données sont passées via postMessage. Les workers ne peuvent pas accéder au DOM.
const worker = new Worker('worker.js');
worker.postMessage({ data: [1, 2, 3] });
worker.onmessage = (e) => console.log('Result:', e.data);
// worker.js
self.onmessage = (e) => {
self.postMessage(e.data.data.map(x => x ** 2));
};Chargement paresseux
IntersectionObserver se déclenche lorsque les éléments entrent dans le viewport. data-src détient la vraie URL ; src est défini lorsque visible. Réduit le chargement de page initial pour les pages lourdes en images.
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));Mises à jour DOM par lot
DocumentFragment regroupe les insertions DOM en un seul reflow, beaucoup plus rapide que d'ajouter un par un. Minimise le thrashing de mise en page.
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
list.appendChild(fragment); // Single reflowGestion de la mémoire
WeakMap permet le GC des clés, empêchant les fuites. Supprimez toujours les écouteurs d'événement lorsque les éléments sont supprimés. Définissez les gros objets à null pour le GC.
const cache = new WeakMap();
cache.set(element, data);
// When element is GC'd, entry is removed
element.removeEventListener('click', handler);
bigArray = null; // Allow GCSécurité (XSS/CSRF)
Prévention XSS
XSS injecte des scripts malveillants via l'entrée utilisateur. N'utilisez jamais innerHTML avec des données non fiables. textContent est sûr. Les en-têtes CSP restreignent les sources de scripts.
// BAD: vulnerable to XSS
element.innerHTML = userInput;
// GOOD: safe alternatives
element.textContent = userInput;
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}Prévention CSRF
CSRF trompe les utilisateurs pour qu'ils fassent des actions non désirées. Les jetons garantissent que les requêtes viennent de votre app. Les cookies SameSite=Strict ne sont pas envoyés cross-site. Utilisez des jetons CSRF pour les opérations modifiant l'état.
const token = document.cookie.match(/csrf_token=([^;]+)/)?.[1];
fetch('/api/data', {
method: 'POST',
headers: { 'X-CSRF-Token': token }
});
// SameSite cookie: Set-Cookie: session=abc; SameSite=StrictContent Security Policy
CSP restreint quelles ressources peuvent se charger. default-src est le repli. script-src contrôle JavaScript. Commencez par le mode report-only avant d'appliquer.
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data:">Cookies sécurisés
HttpOnly empêche l'accès JavaScript aux cookies, atténuant XSS. Secure garantit HTTPS uniquement. SameSite=Strict empêche CSRF. Les cookies de session devraient toujours utiliser les deux.
// Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
// HttpOnly: not accessible via JavaScript
// Secure: only sent over HTTPS
// SameSite: Strict | Lax | None
document.cookie // Cannot see HttpOnly cookiesValidation d'entrée
La validation côté client améliore l'UX mais n'est pas de la sécurité. Validez toujours côté serveur. Utilisez DOMPurify pour l'assainissement HTML. Liste blanche des balises autorisées.
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function sanitize(html) {
return DOMPurify.sanitize(html, { ALLOWED_TAGS: ['p', 'br', 'strong'] });
}
// Never trust client input - always validate on server tooPatrons de conception
Singleton
Singleton garantit qu'une seule instance existe. Le constructeur renvoie l'instance existante. Utile pour la configuration, la journalisation et les connexions de base de données.
class Config {
constructor() {
if (Config.instance) return Config.instance;
this.settings = {};
Config.instance = this;
}
}
const c1 = new Config();
const c2 = new Config();
console.log(c1 === c2); // trueObserver/Pub-Sub
Le patron Observer permet aux objets de s'abonner à des événements. on enregistre, emit déclenche, off désabonne. Fondation des architectures pilotées par événements.
class EventEmitter {
constructor() { this.events = {}; }
on(event, cb) { (this.events[event] ||= []).push(cb); }
emit(event, data) { (this.events[event] || []).forEach(cb => cb(data)); }
off(event, cb) { this.events[event] = this.events[event]?.filter(c => c !== cb); }
}Fabrique
La Fabrique crée des objets sans exposer la logique d'instanciation. L'appelant spécifie un type, la fabrique décide quelle classe instancier.
class Dog { speak() { return 'Woof'; } }
class Cat { speak() { return 'Meow'; } }
function AnimalFactory(type) {
switch (type) {
case 'dog': return new Dog();
case 'cat': return new Cat();
}
}Patron Module
Le patron Module encapsule l'état privé en utilisant des fermetures. IIFE crée une portée privée. Seules les méthodes renvoyées sont publiques.
const counter = (() => {
let count = 0; // Private
return {
increment: () => ++count,
getCount: () => count
};
})();
counter.increment();
console.log(counter.getCount()); // 1Stratégie
Le patron Stratégie encapsule des algorithmes interchangeables. Le contexte délègue à la stratégie sélectionnée. Évite les grandes chaînes if/else.
const strategies = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
};
function calculate(strategy, a, b) {
return strategies[strategy]?.(a, b);
}Pièges courants
Liaison this
this est déterminé par la façon dont une fonction est appelée. Les fonctions fléchées héritent this de la portée englobante. Les méthodes perdent this lorsqu'elles sont détachées. Utilisez bind.
const obj = {
name: 'Alice',
greet: function() { console.log(this.name); },
arrow: () => console.log(this.name)
};
obj.greet(); // Alice
obj.arrow(); // undefined
const fn = obj.greet;
fn(); // undefined (lost binding)Virgule flottante
JavaScript utilise la virgule flottante IEEE 754. Utilisez Number.EPSILON pour les comparaisons, ou multipliez par des puissances de 10 pour travailler avec des entiers.
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
const eps = Number.EPSILON;
console.log(Math.abs(0.1 + 0.2 - 0.3) < eps); // true== vs ===
== effectue la coercion de type, menant à des résultats surprenants. === vérifie à la fois le type et la valeur sans coercion. Utilisez toujours === et !==.
console.log(0 == false); // true
console.log('' == false); // true
console.log(null == undefined); // true
console.log(0 === false); // false
console.log('' === false); // false
// Always use ===Fermetures dans les boucles
var a une portée de fonction, donc toutes les fermetures partagent la même variable. let a une portée de bloc, créant une nouvelle liaison par itération. Utilisez toujours let/const.
// BUG: all print 3
for (var i = 0; i < 3; i++)
setTimeout(() => console.log(i), 100);
// FIX: let
for (let i = 0; i < 3; i++)
setTimeout(() => console.log(i), 100);Gestion d'erreurs async
Les rejets de promesse non gérés peuvent faire planter Node.js. Enveloppez toujours await dans try/catch, ou utilisez .catch(). Écoutez les événements unhandledRejection.
// BUG: unhandled rejection
async function fetchData() {
const data = await fetch('/api');
return data.json();
}
// FIX: try/catch
async function fetchDataSafe() {
try { return await (await fetch('/api')).json(); }
catch (err) { console.error('Failed:', err); return null; }
}Snippets JavaScript associés
Copy-paste ready code for common tasks.
Tableaux : map, filter, reduce
Enchaîner map, filter, reduce, find, some et every sur des tableaux.
Déduplication de tableau
Dédupliquer un tableau en utilisant Set.
Clonage profond
Cloner profondément des objets, en prenant en charge les types de données courants.
Fonction debounce
Attendre un certain temps après le déclenchement d'un événement avant d'exécuter ; réinitialiser le minuteur si déclenché à nouveau pendant l'attente.
Fonction throttle
Limiter une fonction à s'exécuter au plus une fois dans un intervalle de temps.
Contrôle de concurrence Promise.all
Un exécuteur de Promise avec limite de concurrence.
Gestion d'erreurs async/await
Wrapper des fonctions async pour capturer uniformément les exceptions.
Wrapper Fetch
Wrapper fetch avec timeout, gestion d'erreurs et analyse JSON.
Opérations localStorage
Wrapper localStorage avec délai d'expiration et prise en charge JSON.
Opérations Cookie
Wrapper des opérations de lecture, écriture et suppression de Cookie.
Analyse des paramètres URL
Analyser la chaîne de requête URL en un objet.
Formatage de date
Formater une date en une chaîne spécifiée.
Formatage monétaire
Formater un nombre en chaîne monétaire avec séparateur de milliers.
Génération de nombres aléatoires
Générer des nombres aléatoires et des chaînes aléatoires dans une plage.
Conversion de couleur
Convertir entre couleurs RGB et HEX.
Génération d'UUID
Générer un identifiant unique conforme à UUID v4.
Troncature de chaîne
Tronquer une chaîne et ajouter des points de suspension.
Aplatissement de tableau
Aplatir un tableau multidimensionnel en une dimension.
Fusion d'objets
Fusionner profondément plusieurs objets.
Vérification de type
Déterminer précisément les types de données JavaScript.
Délégation d'événements
Implémenter la délégation d'événements via le bouillonnement d'événements.
Manipulation DOM
Créer et manipuler dynamiquement des éléments DOM.
Validation de formulaire
Une collection de règles de validation de formulaire courantes.
Téléversement de fichier
Wrapper le téléversement de fichier avec progression et prise en charge du découpage.
Chargement différé d'images
Implémenter le chargement différé d'images avec IntersectionObserver.
Copier dans le presse-papiers
Une méthode de copie dans le presse-papiers multi-navigateur.
API Fullscreen
Wrapper les opérations plein écran du navigateur.
Géolocalisation
Obtenir les informations de géolocalisation de l'utilisateur.
Web Worker
Créer un Web Worker pour exécuter des tâches chronophages.
Service Worker
Enregistrer un Service Worker pour la mise en cache hors ligne.
Opérations IndexedDB
Wrapper les opérations CRUD IndexedDB.
Dessin Canvas
Exemple de base de dessin Canvas.
Was this helpful?