Que se passe-t-il quand ce code est exécuté ?
let a = 1;
const b = 2;
a = 3;
b = 4;
console.log(a, b);let autorise la réassignation, const non. Réassigner b lève une TypeError avant tout console.log.
Chaque question JavaScript avec la bonne réponse et une explication claire.
let a = 1;
const b = 2;
a = 3;
b = 4;
console.log(a, b);let autorise la réassignation, const non. Réassigner b lève une TypeError avant tout console.log.
console.log(0 == "0");
console.log(0 === "0");L'égalité lâche (==) convertit les types, donc 0 == "0" est true. L'égalité stricte (===) ne convertit pas, donc 0 === "0" est false.
const arr = [1, 2, 3];
arr[10] = 99;
console.log(arr.length);Affecter à l'indice 10 étend le tableau. La longueur passe à 11 ; les indices 3–9 sont creux (slots vides).
const s = "hello";
console.log(s[0], s.length);Les chaînes supportent l'accès par indice (s[0] = 'h') et ont une propriété length. "hello" fait 5 caractères.
const obj = { name: "Ada" };
const { name, age = 30 } = obj;
console.log(name, age);La déstructuration avec valeur par défaut ne l'utilise que si la propriété est undefined. age n'est pas dans obj, donc il prend 30.
console.log(typeof null);typeof null renvoie "object" — une vieille bizarrerie JavaScript conservée pour la compatibilité.
console.log(NaN === NaN);
console.log(Number.isNaN(NaN));NaN est la seule valeur non égale à elle-même. Utilise Number.isNaN (ou Object.is) pour le détecter.
console.log(null == undefined);
console.log(null === undefined);
console.log(null == 0);== considère null et undefined égaux entre eux mais à rien d'autre (pas de conversion vers 0).
const a = [1, 2, 3, 4, 5];
a.length = 2;
console.log(a);Affecter une valeur plus petite à length tronque le tableau — les éléments supplémentaires sont entièrement supprimés.
const a = new Array(3);
const b = Array.of(3);
console.log(a.length, b.length);new Array(3) traite un seul nombre comme la longueur (3 slots vides). Array.of(3) le traite comme un unique élément.
let s = "hello";
s[0] = "H";
console.log(s);Les chaînes sont immuables. L'affectation échoue silencieusement (en strict mode, elle lève) — la chaîne reste "hello".
const x = 10;
console.log(`x is ${x > 5 ? "big" : "small"}`);Les template literals évaluent toute expression dans ${...}, ternaires inclus.
const o = { 2: "a", 1: "b", "x": "c" };
console.log(Object.keys(o));Les clés de type entier viennent d'abord en ordre croissant, puis les clés string dans l'ordre d'insertion.
function fn(...args) { return args.length; }
const arr = [1, 2, 3];
console.log(fn(...arr));Le spread (...arr) à l'appel passe les éléments comme arguments individuels ; rest (...args) les collecte dans la fonction.
const x = 5;
const label = x > 0 ? "+" : x < 0 ? "-" : "0";
console.log(label);Les ternaires imbriqués s'évaluent de droite à gauche. x > 0 est true, donc la première branche gagne : "+".
const a = 0 || "fallback";
const b = "" || "fallback";
const c = "value" || "fallback";
console.log(a, b, c);|| renvoie le premier opérande truthy ou le dernier. 0 et "" sont falsy, donc le fallback l'emporte pour a et b.
const a = 0 ?? "fallback";
const b = null ?? "fallback";
const c = undefined ?? "fallback";
console.log(a, b, c);?? ne déclenche le fallback que pour null ou undefined — 0 est conservé (contrairement à ||).
console.log(Boolean(""), Boolean("0"), Boolean([]), Boolean({}));Valeurs falsy : "", 0, NaN, null, undefined, false. Les chaînes non vides ET tout objet (y compris [] et {}) sont truthy.
console.log(1 + "2" + 3);
console.log(1 + 2 + "3");+ est associatif à gauche. Dès qu'un opérande string apparaît, le reste se concatène. "1"+"2"+3 → "123" ; 1+2 puis +"3" → "33".
console.log(parseInt("08"));
console.log(parseInt("08", 10));Le parseInt moderne utilise par défaut la base 10 pour les chaînes non-0x (l'ancien octal par défaut pour 0 initial a disparu en ES5+). Passe toujours une base pour la clarté.
const a = Array.from({ length: 3 }, (_, i) => i * 2);
console.log(a);Array.from avec un objet n'ayant que length plus un mapper est la façon canonique de construire un tableau de N valeurs calculées.
const a = [1, NaN, 3];
console.log(a.indexOf(NaN), a.includes(NaN));indexOf utilise === (NaN ne s'égale jamais lui-même). includes utilise SameValueZero, qui considère NaN égal à NaN.
const name = "Ada", age = 36;
const user = { name, age, role: "admin" };
console.log(user.name, user.role);Le raccourci de propriété { name } équivaut à { name: name } — utilise la valeur de la variable.
const o = { x: 1 };
o.x = 2;
o.y = 3;
console.log(o.x, o.y);const protège la liaison (tu ne peux pas réassigner o), mais l'objet lui-même reste mutable.
const arr = ["a", "b", "c"];
arr.extra = "x";
const ofs = []; for (const v of arr) ofs.push(v);
const ins = []; for (const k in arr) ins.push(k);
console.log(ofs.length, ins.length);for...of itère les valeurs indexées du tableau (3). for...in parcourt toutes les clés string énumérables, y compris "extra" ajoutée (4).
function makeCounter() {
let n = 0;
return () => ++n;
}
const c = makeCounter();
c(); c();
console.log(c());La closure capture n. Chaque appel l'incrémente ; la troisième invocation renvoie 3.
console.log(typeof a);
var a = 1;
console.log(typeof b);Les déclarations var sont hoistées (initialisées à undefined). typeof sur un nom non déclaré b renvoie 'undefined' sans lever d'erreur.
const obj = {
name: "Ada",
greet() { return `hi ${this.name}`; }
};
const g = obj.greet;
console.log(g());Les méthodes détachées perdent leur this. Dans un module / contexte strict, this est undefined et accéder à .name lève ; en non-strict navigateur c'est window, où window.name vaut '' mais ressort comme undefined pour notre cas — la plupart des environnements modernes donnent 'hi undefined'.
const a = [1, 2];
const b = [3, 4];
const c = [...a, ...b, 5];
console.log(c.length, c[2]);Le spread aplatit les deux tableaux dans c, puis ajoute 5 : [1, 2, 3, 4, 5]. Longueur 5, indice 2 vaut 3.
function f(a, b = 2, ...rest) {
return [a, b, rest];
}
console.log(f(1, undefined, 3, 4));Les valeurs par défaut s'activent sur undefined, donc b devient 2. Le rest parameter collecte les arguments restants dans un tableau.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}var est function-scoped — tous les callbacks ferment sur le même i, qui vaut 3 quand ils s'exécutent.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}let crée une nouvelle liaison par itération, donc chaque callback ferme sur son propre i.
const obj = {
name: "Ada",
greet: () => `hi ${this?.name}`,
};
console.log(obj.greet());Les fonctions fléchées ne lient pas leur propre this — il reste celui du scope englobant (ici, top-level → undefined ou globalThis).
class Counter {
n = 0;
inc() { this.n++; }
}
const c = new Counter();
const f = c.inc;
f();
console.log(c.n);Les corps de classe sont en strict mode par défaut. Le f() détaché a this === undefined, donc this.n++ lève TypeError.
const a = { x: { y: 1 } };
const b = { ...a };
b.x.y = 99;
console.log(a.x.y);Le spread fait une copie superficielle — les objets imbriqués sont partagés par référence. Utilise structuredClone pour une copie profonde.
console.log(typeof foo);
console.log(typeof bar);
function foo() {}
var bar = function() {};Les déclarations de fonction sont hoistées avec leur corps. Les function expressions affectées à var ne hoistent que la liaison var (initialement undefined).
const x = (function() {
return 42;
})();
console.log(x);Une IIFE — définie et invoquée immédiatement. Les () finaux appellent la fonction, donc x reçoit la valeur de retour.
function add(a, b) { return a + b; }
const add5 = add.bind(null, 5);
console.log(add5(3));bind applique partiellement les arguments après this. add5 a a fixé à 5, donc add5(3) calcule 5 + 3.
const sum = [1, 2, 3, 4]
.filter(n => n % 2 === 0)
.map(n => n * 10)
.reduce((acc, n) => acc + n, 0);
console.log(sum);filter → [2,4] ; map ×10 → [20,40] ; reduce + → 60.
const o = {
_x: 1,
get x() { return this._x; },
set x(v) { this._x = v * 2; },
};
o.x = 5;
console.log(o.x);Le setter double la valeur avant de la stocker (this._x = 10). Le getter la renvoie ensuite telle quelle.
const data = { user: { profile: { age: 25 } } };
const { user: { profile: { age } } } = data;
console.log(age);La déstructuration imbriquée suit la structure de l'objet — seul age est lié comme variable.
let calls = 0;
function id() { calls++; return calls; }
function f(x = id()) { return x; }
f(); f(10); f();
console.log(calls);Les expressions de paramètre par défaut ne s'évaluent que si l'argument est undefined. f(10) saute le défaut, donc id() ne tourne que deux fois.
console.log([10, 1, 2].sort());Le tri par défaut est lexicographique — les éléments sont comparés comme des chaînes. Utilise .sort((a,b) => a-b) pour un ordre numérique.
const a = { x: 1, y: 2 };
const b = { ...a, x: 99 };
console.log(b);Les propriétés listées après écrasent celles d'avant. L'ordre compte : { x: 99, ...a } laisserait a.x gagner.
const u = { name: "Ada", contact: null };
const email = u?.contact?.email ?? "n/a";
console.log(email);?. court-circuite sur null/undefined en renvoyant undefined ; ?? bascule alors sur "n/a".
const fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns[0](), fns[1](), fns[2]());let crée une nouvelle liaison par itération, donc chaque closure capture son propre i.
const o = {
vals: [1, 2, 3],
sum() {
let total = 0;
this.vals.forEach(function(v) { total += v; });
return total;
}
};
console.log(o.sum());total est capturé par closure (pas via this), donc le this de la fonction interne n'a pas d'importance — la somme est 6.
function head(first, ...rest) { return [first, rest.length]; }
console.log(head("a", "b", "c", "d"));rest collecte tout après first dans un tableau de 3.
const a = Symbol("id");
const b = Symbol("id");
console.log(a === b, a.description === b.description);Chaque appel à Symbol() produit une valeur unique. La description optionnelle n'est qu'une étiquette, sans impact sur l'égalité.
const m = new Map();
m.set(2, "a"); m.set(1, "b"); m.set("x", "c");
console.log([...m.keys()]);Map préserve l'ordre d'insertion pour tout type de clé — contrairement aux objets simples, où les clés de type entier sont réordonnées en croissant.
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");D'abord le code synchrone (1, 4). Puis les microtasks (Promise.then → 3). Enfin les macrotasks (setTimeout → 2).
function* gen() {
yield 1;
yield 2;
return 3;
}
const g = gen();
console.log(g.next().value, g.next().value, g.next().value);Chaque next() renvoie { value, done }. Le troisième appel atteint le return, exposant le 3 renvoyé avec done=true.
class A {
greet() { return "A"; }
}
class B extends A {
greet() { return "B" + super.greet(); }
}
console.log(new B().greet());B.greet renvoie 'B' concaténé avec super.greet() (l'implémentation de A = 'A'), donnant 'BA'.
const target = { x: 1 };
const handler = {
get(obj, prop) { return prop in obj ? obj[prop] : 0; }
};
const p = new Proxy(target, handler);
console.log(p.x, p.y);Le trap get du Proxy renvoie la vraie valeur si la prop existe, sinon 0. p.x → 1, p.y → 0 (y n'est pas sur target).
const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));Les objets simples héritent d'Object.prototype. Object.prototype lui-même est en haut de la chaîne, donc son prototype est null.
function* g() {
yield 1;
yield 2;
return 3;
}
const it = g();
console.log(it.next().value, it.next().value, it.next().value, it.next().value);yield émet des valeurs ; return est la valeur finale (avec done=true). Ensuite, .next() donne { value: undefined, done: true }.
const target = { x: 1 };
const proxy = new Proxy(target, {
get(t, p) { return p in t ? t[p] : "default"; }
});
console.log(proxy.x, proxy.y);Le trap get intercepte les lectures de propriété. Les propriétés existantes passent ; celles manquantes renvoient "default".
const range = {
from: 1, to: 3,
[Symbol.iterator]() {
let cur = this.from, end = this.to;
return {
next: () => cur <= end ? { value: cur++, done: false } : { value: undefined, done: true }
};
}
};
console.log([...range]);Implémenter [Symbol.iterator] rend l'objet itérable — le spread l'invoque et collecte les valeurs jusqu'à done = true.
const o = { x: 1 };
console.log(Reflect.get(o, "x"), Reflect.has(o, "y"));Reflect reflète les opérateurs — Reflect.get est comme obj[p] ; Reflect.has comme "p" in obj.
const wm = new WeakMap();
let key = {};
wm.set(key, "hello");
console.log(wm.get(key));
key = null;
console.log(wm.has({}));Les clés WeakMap doivent être des objets comparés par référence. {} est un nouvel objet — pas la clé d'origine — donc .has renvoie false.
console.log("A");
Promise.resolve().then(() => console.log("B"));
Promise.resolve().then(() => console.log("C"));
console.log("D");Le code synchrone d'abord (A, D), puis les microtasks en FIFO (B, C).
console.log("1");
queueMicrotask(() => console.log("2"));
Promise.resolve().then(() => console.log("3"));
console.log("4");queueMicrotask et Promise.then planifient tous deux des microtasks. Elles s'exécutent après le code sync, dans l'ordre de planification.
class Box {
#value;
constructor(v) { this.#value = v; }
get() { return this.#value; }
}
const b = new Box(42);
console.log(b.get(), b["#value"]);Les champs privés (#) ne sont accessibles que dans le corps de classe ; b["#value"] lit une propriété string normale qui n'existe pas.
class A {
constructor() { this.tag = "A"; }
}
class B extends A {
constructor() {
super();
this.tag = "B";
}
}
console.log(new B().tag);super() exécute le constructeur de A (définit this.tag = "A") ; l'affectation suivante l'écrase en "B".
class Animal {
static [Symbol.hasInstance]() { return true; }
}
console.log({} instanceof Animal);instanceof délègue à Symbol.hasInstance — une implémentation personnalisée peut renvoyer ce qu'elle veut.
const o = Object.create(null);
console.log(Object.getPrototypeOf(o));
console.log(o.toString);Object.create(null) crée un objet sans prototype — pas d'Object.prototype.toString à hériter.
function show() { return [this.tag, ...arguments]; }
console.log(show.call({ tag: "A" }, 1, 2));
console.log(show.apply({ tag: "B" }, [3, 4]));call prend les arguments en liste ; apply en tableau. Tous deux définissent this avec le premier argument.
const proto = {
get hi() { return `hi ${this.name}`; }
};
const o = Object.create(proto);
o.name = "Ada";
console.log(o.hi);Les getters définis sur le prototype s'exécutent avec this égal au receveur — o, qui a name = "Ada".
const o = {};
Object.defineProperty(o, "x", { value: 1, configurable: false });
try {
Object.defineProperty(o, "x", { value: 2 });
console.log(o.x);
} catch (e) {
console.log("error");
}Un descripteur de données non-configurable permet quand même de changer value (et de basculer writable de true à false). Les autres attributs sont verrouillés.
const o = { a: 1 };
Object.defineProperty(o, "b", { value: 2, enumerable: false });
console.log(Object.keys(o), Object.getOwnPropertyNames(o));Object.keys ne renvoie que les propriétés propres énumérables. getOwnPropertyNames renvoie toutes les propriétés propres à clé string.
function makeIter(arr) {
let i = 0;
return {
next: () => i < arr.length
? { value: arr[i++], done: false }
: { value: undefined, done: true },
[Symbol.iterator]() { return this; },
};
}
console.log([...makeIter(["a","b"])]);Un itérateur qui renvoie lui-même depuis [Symbol.iterator] est aussi itérable, donc spread peut le consommer.
async function* nums() {
yield 1; yield 2;
}
const out = [];
for await (const n of nums()) out.push(n);
console.log(out);for-await-of attend chaque valeur yieldée, donc out collecte les nombres déballés.
const safe = new Proxy({ x: 1 }, {
has(t, p) { return p === "x"; }
});
console.log("x" in safe, "y" in safe);Le trap has intercepte l'opérateur in. On n'autorise que "x".
const o = Object.freeze({ x: 1, nested: { y: 2 } });
o.x = 99;
o.nested.y = 99;
console.log(o.x, o.nested.y);Object.freeze est superficiel — le x du haut est verrouillé, mais l'objet imbriqué reste mutable.
class A {}
A.prototype.constructor = function() { return { tag: "fake" }; };
console.log(new A().tag);new A() appelle A directement (le constructeur de classe d'origine), pas la propriété constructor sur le prototype.
async function f() { return 42; }
const r = f();
console.log(r);Appeler une fonction async renvoie une Promise. Comme la fonction retourne de façon synchrone, la Promise est déjà fulfilled avec 42 (affiché Promise { 42 }).
Promise.all([
Promise.resolve(1),
Promise.reject("err"),
Promise.resolve(3),
]).then((v) => console.log("ok", v))
.catch((e) => console.log("fail", e));Promise.all reject au premier rejet — le catch se déclenche avec cette raison, et les valeurs fulfilled sont jetées.
async function main() {
console.log("a");
await Promise.resolve();
console.log("b");
}
main();
console.log("c");Le code synchrone de main d'abord ('a'), puis await rend le contrôle. 'c' s'affiche, puis la microtask reprend main et affiche 'b'.
Promise.allSettled([
Promise.resolve(1),
Promise.reject("x"),
]).then((arr) => console.log(arr.map(r => r.status)));Promise.allSettled se résout avec un tableau de { status, value | reason }. Status vaut 'fulfilled' ou 'rejected'.
Après chaque macrotask, le moteur vide la file des microtasks avant le macrotask suivant ou le rendu. setTimeout(0) est un macrotask et attend au moins un cycle.
const a = new Promise(r => setTimeout(() => r("a"), 50));
const b = new Promise(r => setTimeout(() => r("b"), 10));
const winner = await Promise.race([a, b]);
console.log(winner);Promise.race se résout avec l'entrée qui s'installe en premier — b s'installe après 10ms, a après 50ms.
async function f() { return 5; }
const r = f();
console.log(r instanceof Promise, await r);Une fonction async renvoie toujours une Promise — même si le corps renvoie une valeur brute, elle est emballée.
async function f() {
try {
await Promise.reject(new Error("boom"));
} catch (e) {
return "caught:" + e.message;
}
}
console.log(await f());await sur une promise rejetée lève de façon synchrone à l'intérieur de la fonction async — try/catch la gère.
async function f() {
throw new Error("x");
}
try {
f();
console.log("after call");
} catch (e) {
console.log("caught");
}Une fonction async qui retourne un reject n'est PAS un throw synchrone — sans await, l'erreur devient un unhandled rejection.
try {
await Promise.all([
Promise.resolve(1),
Promise.reject(new Error("nope")),
Promise.resolve(3),
]);
} catch (e) {
console.log("caught:", e.message);
}Promise.all reject dès qu'une entrée rejette — le rejet court-circuite l'attente.
try {
await Promise.any([
Promise.reject("a"),
Promise.reject("b"),
]);
} catch (e) {
console.log(e.constructor.name, e.errors);
}Promise.any reject avec une AggregateError contenant toutes les raisons uniquement si toutes les entrées rejettent.
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("sync");Le code sync d'abord, puis les microtasks (Promise.then), puis les macrotasks (setTimeout).
const r = await Promise.resolve(1)
.then(v => v + 1)
.then(v => v * 10);
console.log(r);Chaque .then voit la valeur de retour précédente : 1 → 2 → 20.
async function getX() { return 5; }
async function run() {
const x = getX();
console.log(typeof x.then, x + 1);
}
await run();Oublier await laisse x en Promise. x + 1 le convertit via toString() → "[object Promise]1".
async function run() {
const items = [1, 2, 3];
const out = [];
for (const n of items) {
out.push(await Promise.resolve(n * 2));
}
return out;
}
console.log(await run());await dans for…of s'exécute séquentiellement. Chaque itération attend la promise précédente — out reçoit les valeurs déballées.
async function run() {
const items = [1, 2, 3];
return Promise.all(items.map(async n => n * 2));
}
console.log(await run());Map+async lance les fonctions async en parallèle, chacune renvoyant une Promise. Promise.all les déballe toutes.
const out = await Promise.allSettled([
Promise.resolve(1),
Promise.reject("x"),
]);
console.log(out.map(r => r.status));Promise.allSettled ne rejette jamais — elle renvoie des objets de forme { status: 'fulfilled' | 'rejected', value | reason }.
function delay(ms) {
return new Promise(r => setTimeout(r, ms));
}
const start = Date.now();
await Promise.all([delay(50), delay(50), delay(50)]);
const elapsed = Date.now() - start;
console.log(elapsed >= 50 && elapsed < 200);Les trois délais tournent en parallèle, donc la durée totale est à peu près la plus longue (~50ms), pas 150ms.
try {
await Promise.resolve(1).then(v => { throw new Error("oops"); });
} catch (e) {
console.log("caught:" + e.message);
}Lever dans un handler .then transforme la chaîne en rejet, que await reconvertit en throw synchrone.
async function* gen() { yield 1; yield 2; yield 3; }
const out = [];
for await (const n of gen()) out.push(n);
console.log(out);Un async generator yielde des valeurs enveloppées en promise ; for-await-of attend chacune.
console.log("before");
const p = new Promise((res) => {
console.log("executor");
res();
});
console.log("after");
await p;
console.log("done");L'executor de la Promise s'exécute de façon synchrone à la construction de la Promise.
const thenable = { then(res) { res("hi"); } };
const out = await thenable;
console.log(out);await accepte tout thenable. Le moteur appelle .then(resolve, reject) et utilise la valeur résolue.
// Imagine: window.addEventListener('unhandledrejection', e => console.log('unhandled'));
Promise.reject(new Error("boom"));Un rejet sans .catch / await déclenche l'événement unhandledrejection sur window (ou process en Node).
function defer() {
let resolve;
const promise = new Promise(r => { resolve = r; });
return { promise, resolve };
}
const d = defer();
setTimeout(() => d.resolve(42), 10);
console.log(await d.promise);Capturer le resolver permet de settle une Promise depuis l'extérieur de l'executor — le pattern deferred classique.
let a;
queueMicrotask(() => a = "qm");
Promise.resolve().then(() => a = "p");
await Promise.resolve();
console.log(a);Les deux planifient des microtasks. Elles s'exécutent en FIFO : queueMicrotask met "qm" d'abord, puis le callback .then écrase avec "p".
const user = { name: "Ada", profile: null };
console.log(user?.profile?.email ?? "n/a");L'optional chaining court-circuite à profile (null) en donnant undefined. ?? bascule sur 'n/a' car le côté gauche est nullish.
console.log(0 ?? "fallback");
console.log("" ?? "fallback");
console.log(null ?? "fallback");?? ne bascule que pour null ou undefined. 0 et "" sont conservés tels quels (contrairement à ||, qui les remplacerait).
const fn = null;
console.log(fn?.());L'appel optionnel (?.()) court-circuite à undefined quand l'appelé est null/undefined au lieu de lever TypeError.
const entries = [["a", 1], ["b", 2]];
const obj = Object.fromEntries(entries);
console.log(obj.a, obj.b);Object.fromEntries inverse Object.entries — il construit un objet à partir de paires clé/valeur.
const a = { x: { y: 1 } };
const b = structuredClone(a);
b.x.y = 99;
console.log(a.x.y);structuredClone produit une copie profonde. Modifier b.x.y n'affecte pas a, donc a.x.y reste 1. Disponible dans Node et les navigateurs modernes sans polyfill.
const obj = { greet: null };
console.log(obj.greet?.());
console.log(obj.missing?.());?.() court-circuite à undefined quand la valeur précédente est null ou undefined — aucune erreur levée.
const arr = [1];
const [a, b = 99, c = 100] = arr;
console.log(a, b, c);Les valeurs par défaut en déstructuration de tableau s'activent sur undefined — b et c prennent leurs valeurs par défaut.
const m = new Map([["a", 1], ["b", 2]]);
const o = Object.fromEntries(m);
console.log(o.a, o.b);Object.fromEntries accepte tout itérable de paires [clé, valeur] — y compris les Map.
let a = "";
let b = "value";
a ||= "fallback";
b ||= "fallback";
console.log(a, b);||= n'affecte que si le côté gauche est falsy. "" est falsy → a devient "fallback" ; "value" est truthy → b reste inchangé.
let a = 0;
let b = null;
a ??= 99;
b ??= 99;
console.log(a, b);??= n'affecte que si le côté gauche est null ou undefined. 0 est conservé ; null est remplacé.
const big = 1_000_000;
console.log(big === 1000000);Les séparateurs numériques (_) sont du sucre syntaxique — ils sont retirés au parsing et n'affectent pas la valeur.
const a = 9007199254740993n;
const b = a + 1n;
console.log(b);BigInt gère exactement les entiers de taille arbitraire. Le 'n' final marque un littéral BigInt.
const arr = [10, 20, 30];
console.log(arr.at(-1), arr.at(-2));Array.prototype.at supporte les indices négatifs, comptés depuis la fin.
console.log("a-b-c".replaceAll("-", "/"));String.prototype.replaceAll remplace chaque occurrence sans avoir besoin d'une regex globale.
const o = { x: 1 };
console.log(Object.hasOwn(o, "x"), Object.hasOwn(o, "toString"));Object.hasOwn ne vérifie que les propriétés propres — "toString" est hérité d'Object.prototype.
console.log([1, [2, [3, [4]]]].flat(2));flat(depth) aplatit jusqu'à depth niveaux. flat(2) laisse le [4] le plus profond intact. Utilise flat(Infinity) pour aplatir totalement.
console.log([1, 2, 3].flatMap(n => [n, n * 10]));flatMap est map suivi de flat(1) — utile pour les transformations un-vers-plusieurs.
const winner = await Promise.any([
Promise.reject("a"),
Promise.resolve("b"),
Promise.resolve("c"),
]);
console.log(winner);Promise.any se résout avec la première valeur fulfilled, en ignorant les rejets.
globalThis.__token = 42;
console.log(globalThis.__token);globalThis est la poignée standardisée vers l'objet global — fonctionne dans le navigateur, Node, les workers et Deno.
// In an ES module:
// const data = await fetch("/api").then(r => r.json());
console.log("module loaded after data");Le top-level await suspend l'évaluation du module. Les modules qui l'importent attendent avant d'exécuter leur propre corps.
const items = [1, 2, 3, 4];
const grouped = Object.groupBy(items, n => n % 2 === 0 ? "even" : "odd");
console.log(grouped.even, grouped.odd);Object.groupBy (ES2024) regroupe les éléments par la valeur retournée par le callback, en renvoyant un objet simple indexé par groupe.
let target = { name: "Ada" };
const ref = new WeakRef(target);
console.log(ref.deref()?.name);WeakRef.deref() renvoie la cible tant qu'elle est encore atteignable. Une fois collectée par le GC, deref() renvoie undefined.
const d = new Date(2026, 0, 15);
const c = structuredClone({ when: d });
console.log(c.when instanceof Date, c.when.getTime() === d.getTime());structuredClone préserve les types intégrés comme Date, Map, Set, RegExp, ArrayBuffer — pas seulement les objets simples.
console.log([1, 2, 3, 4].findLast(n => n < 3));findLast parcourt de droite à gauche et renvoie le dernier élément qui matche — ici 2 (la dernière valeur < 3).
function sum(a, b, c) { return a + b + c; }
const args = [1, 2, 3];
console.log(sum(...args));Le spread à l'appel développe l'itérable en arguments individuels — a=1, b=2, c=3.