Was passiert, wenn dieser Code ausgeführt wird?
let a = 1;
const b = 2;
a = 3;
b = 4;
console.log(a, b);let erlaubt Neuzuweisung, const nicht. Die Neuzuweisung von b wirft TypeError, bevor console.log läuft.
Jede JavaScript-Frage mit richtiger Antwort und klarer Erklärung.
let a = 1;
const b = 2;
a = 3;
b = 4;
console.log(a, b);let erlaubt Neuzuweisung, const nicht. Die Neuzuweisung von b wirft TypeError, bevor console.log läuft.
console.log(0 == "0");
console.log(0 === "0");Lockere Gleichheit (==) konvertiert Typen, also 0 == "0" ist true. Strikte Gleichheit (===) konvertiert nicht, also 0 === "0" ist false.
const arr = [1, 2, 3];
arr[10] = 99;
console.log(arr.length);Eine Zuweisung an Index 10 erweitert das Array. Die Länge wird 11; Indizes 3–9 sind sparse (leere Slots).
const s = "hello";
console.log(s[0], s.length);Strings unterstützen Indexzugriff (s[0] = 'h') und haben eine length-Property. "hello" hat 5 Zeichen.
const obj = { name: "Ada" };
const { name, age = 30 } = obj;
console.log(name, age);Destructuring mit Default-Wert verwendet diesen nur, wenn die Property undefined ist. age fehlt in obj, also wird 30 genutzt.
console.log(typeof null);typeof null liefert "object" — eine alte JavaScript-Eigenheit, die aus Kompatibilitätsgründen erhalten bleibt.
console.log(NaN === NaN);
console.log(Number.isNaN(NaN));NaN ist der einzige Wert, der sich selbst nicht gleich ist. Nutze Number.isNaN (oder Object.is) zum Erkennen.
console.log(null == undefined);
console.log(null === undefined);
console.log(null == 0);== behandelt null und undefined als gleich zueinander, aber zu nichts sonst (keine Konvertierung zu 0).
const a = [1, 2, 3, 4, 5];
a.length = 2;
console.log(a);Eine kleinere Länge zuweisen kürzt das Array — überzählige Elemente werden komplett entfernt.
const a = new Array(3);
const b = Array.of(3);
console.log(a.length, b.length);new Array(3) interpretiert eine einzelne Zahl als Länge (3 leere Slots). Array.of(3) als einzelnes Element.
let s = "hello";
s[0] = "H";
console.log(s);Strings sind unveränderlich. Die Zuweisung schlägt still fehl (im strict mode wirft sie) — der String bleibt "hello".
const x = 10;
console.log(`x is ${x > 5 ? "big" : "small"}`);Template Literals werten jeden Ausdruck in ${...} aus, auch Ternäre.
const o = { 2: "a", 1: "b", "x": "c" };
console.log(Object.keys(o));Integer-artige Keys kommen zuerst aufsteigend sortiert, dann String-Keys in Einfügereihenfolge.
function fn(...args) { return args.length; }
const arr = [1, 2, 3];
console.log(fn(...arr));Spread (...arr) am Aufrufort übergibt Elemente als einzelne Argumente; rest (...args) sammelt sie in der Funktion.
const x = 5;
const label = x > 0 ? "+" : x < 0 ? "-" : "0";
console.log(label);Verschachtelte Ternäre werden von rechts nach links ausgewertet. x > 0 ist true, also gewinnt der erste Zweig: "+".
const a = 0 || "fallback";
const b = "" || "fallback";
const c = "value" || "fallback";
console.log(a, b, c);|| gibt den ersten truthy Operanden zurück oder den letzten. 0 und "" sind falsy, also gewinnt der Fallback bei a und b.
const a = 0 ?? "fallback";
const b = null ?? "fallback";
const c = undefined ?? "fallback";
console.log(a, b, c);?? greift nur bei null oder undefined zum Fallback — 0 bleibt erhalten (anders als bei ||).
console.log(Boolean(""), Boolean("0"), Boolean([]), Boolean({}));Falsy-Werte: "", 0, NaN, null, undefined, false. Nicht-leere Strings UND jedes Objekt (auch [] und {}) sind truthy.
console.log(1 + "2" + 3);
console.log(1 + 2 + "3");+ ist linksassoziativ. Sobald ein String-Operand auftaucht, wird der Rest verkettet. "1"+"2"+3 → "123"; 1+2 dann +"3" → "33".
console.log(parseInt("08"));
console.log(parseInt("08", 10));Modernes parseInt nutzt für nicht-0x-Strings standardmäßig Radix 10 (der alte Octal-Default für führende 0 wurde in ES5+ entfernt). Übergib trotzdem immer einen Radix.
const a = Array.from({ length: 3 }, (_, i) => i * 2);
console.log(a);Array.from mit einem Objekt, das nur length hat, plus Mapper ist der kanonische Weg, ein Array aus N berechneten Werten zu bauen.
const a = [1, NaN, 3];
console.log(a.indexOf(NaN), a.includes(NaN));indexOf nutzt === (NaN gleicht sich nie selbst). includes nutzt SameValueZero, das NaN gleich NaN behandelt.
const name = "Ada", age = 36;
const user = { name, age, role: "admin" };
console.log(user.name, user.role);Property-Shorthand { name } entspricht { name: name } — nutzt den Wert der Variable.
const o = { x: 1 };
o.x = 2;
o.y = 3;
console.log(o.x, o.y);const schützt die Bindung (du kannst o nicht neu zuweisen), aber das Objekt selbst bleibt veränderlich.
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 iteriert die indizierten Werte des Arrays (3). for...in läuft über alle aufzählbaren String-Keys, inklusive des hinzugefügten "extra" (4).
function makeCounter() {
let n = 0;
return () => ++n;
}
const c = makeCounter();
c(); c();
console.log(c());Die Closure fängt n ein. Jeder Aufruf erhöht es; der dritte Aufruf gibt 3 zurück.
console.log(typeof a);
var a = 1;
console.log(typeof b);var-Deklarationen werden gehoistet (mit undefined initialisiert). typeof auf einen nicht deklarierten Namen b liefert 'undefined' ohne Fehler.
const obj = {
name: "Ada",
greet() { return `hi ${this.name}`; }
};
const g = obj.greet;
console.log(g());Losgelöste Methoden verlieren ihr this. In einem Modul / Strict-Kontext ist this undefined und der Zugriff auf .name wirft; in non-strict-Browser-Globals ist es window, wo window.name standardmäßig '' ist, aber für unseren Zweck als undefined gelesen wird — die meisten modernen Umgebungen liefern 'hi undefined'.
const a = [1, 2];
const b = [3, 4];
const c = [...a, ...b, 5];
console.log(c.length, c[2]);Spread flacht beide Arrays in c ab und hängt 5 an: [1, 2, 3, 4, 5]. Länge 5, Index 2 ist 3.
function f(a, b = 2, ...rest) {
return [a, b, rest];
}
console.log(f(1, undefined, 3, 4));Default-Werte greifen bei undefined, also wird b zu 2. Der Rest-Parameter sammelt die übrigen Args in einem Array.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}var ist function-scoped — alle Callbacks schließen über dasselbe i, das beim Ausführen bereits 3 ist.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}let erzeugt pro Iteration eine neue Bindung, also schließt jeder Callback über sein eigenes i.
const obj = {
name: "Ada",
greet: () => `hi ${this?.name}`,
};
console.log(obj.greet());Pfeilfunktionen binden kein eigenes this — es bleibt das des umschließenden Scopes (hier top-level → undefined oder globalThis).
class Counter {
n = 0;
inc() { this.n++; }
}
const c = new Counter();
const f = c.inc;
f();
console.log(c.n);Klassenkörper sind standardmäßig im strict mode. Das losgelöste f() hat this === undefined, also wirft this.n++ TypeError.
const a = { x: { y: 1 } };
const b = { ...a };
b.x.y = 99;
console.log(a.x.y);Spread macht eine flache Kopie — verschachtelte Objekte werden per Referenz geteilt. Nutze structuredClone für tiefe Kopie.
console.log(typeof foo);
console.log(typeof bar);
function foo() {}
var bar = function() {};Function Declarations werden mit Body gehoistet. Function Expressions, die var zugewiesen werden, hoisten nur die var-Bindung (anfangs undefined).
const x = (function() {
return 42;
})();
console.log(x);Eine IIFE — definiert und sofort aufgerufen. Das abschließende () ruft die Funktion auf, also bekommt x den Rückgabewert.
function add(a, b) { return a + b; }
const add5 = add.bind(null, 5);
console.log(add5(3));bind wendet Argumente nach this teilweise an. add5 hat a fest auf 5, also rechnet add5(3) 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);Der Setter verdoppelt den Wert vor dem Speichern (this._x = 10). Der Getter gibt ihn dann unverändert zurück.
const data = { user: { profile: { age: 25 } } };
const { user: { profile: { age } } } = data;
console.log(age);Verschachteltes Destructuring folgt der Objektstruktur — nur age wird als Variable gebunden.
let calls = 0;
function id() { calls++; return calls; }
function f(x = id()) { return x; }
f(); f(10); f();
console.log(calls);Default-Parameter-Ausdrücke werden nur ausgewertet, wenn das Argument undefined ist. f(10) überspringt den Default, also läuft id() nur zweimal.
console.log([10, 1, 2].sort());Standard-Sort ist lexikographisch — Elemente werden als Strings verglichen. Nutze .sort((a,b) => a-b) für numerische Reihenfolge.
const a = { x: 1, y: 2 };
const b = { ...a, x: 99 };
console.log(b);Später aufgeführte Properties überschreiben frühere. Reihenfolge zählt: { x: 99, ...a } ließe a.x gewinnen.
const u = { name: "Ada", contact: null };
const email = u?.contact?.email ?? "n/a";
console.log(email);?. kurzschließt bei null/undefined und liefert undefined; ?? fällt dann auf "n/a" zurück.
const fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns[0](), fns[1](), fns[2]());let erzeugt pro Iteration eine neue Bindung, also fängt jede Closure ihr eigenes i ein.
const o = {
vals: [1, 2, 3],
sum() {
let total = 0;
this.vals.forEach(function(v) { total += v; });
return total;
}
};
console.log(o.sum());total wird per Closure eingefangen (nicht über this), also ist das this der inneren Funktion egal — die Summe ist 6.
function head(first, ...rest) { return [first, rest.length]; }
console.log(head("a", "b", "c", "d"));rest sammelt alles nach first in einem Array der Länge 3.
const a = Symbol("id");
const b = Symbol("id");
console.log(a === b, a.description === b.description);Jeder Symbol()-Aufruf liefert einen eindeutigen Wert. Die optionale description ist nur ein Label und wirkt sich nicht auf Gleichheit aus.
const m = new Map();
m.set(2, "a"); m.set(1, "b"); m.set("x", "c");
console.log([...m.keys()]);Map bewahrt die Einfügereihenfolge für jeden Key-Typ — anders als einfache Objekte, bei denen integer-artige Keys aufsteigend umsortiert werden.
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");Zuerst synchroner Code (1, 4). Dann Microtasks (Promise.then → 3). Dann 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);Jedes next() liefert { value, done }. Der dritte Aufruf trifft das return und liefert die 3 mit done=true.
class A {
greet() { return "A"; }
}
class B extends A {
greet() { return "B" + super.greet(); }
}
console.log(new B().greet());B.greet liefert 'B' verkettet mit super.greet() (A's Implementation = 'A'), ergibt '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);Die get-Trap des Proxy liefert den echten Wert, wenn die Prop existiert, sonst 0. p.x → 1, p.y → 0 (y ist nicht auf target).
const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));Einfache Objekte erben von Object.prototype. Object.prototype selbst steht an der Spitze der Kette, also ist sein Prototyp 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 emittiert Werte; return ist der finale Wert (mit done=true). Danach liefert .next() { 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);Die get-Trap fängt Property-Reads ab. Vorhandene Properties gehen durch; fehlende liefern "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]);Die Implementierung von [Symbol.iterator] macht das Objekt iterierbar — Spread ruft sie auf und sammelt Werte bis done true ist.
const o = { x: 1 };
console.log(Reflect.get(o, "x"), Reflect.has(o, "y"));Reflect spiegelt Operatoren — Reflect.get ist wie obj[p]; Reflect.has wie "p" in obj.
const wm = new WeakMap();
let key = {};
wm.set(key, "hello");
console.log(wm.get(key));
key = null;
console.log(wm.has({}));WeakMap-Keys müssen Objekte sein, die per Referenz verglichen werden. {} ist ein neues Objekt — nicht der Original-Key — also liefert .has false.
console.log("A");
Promise.resolve().then(() => console.log("B"));
Promise.resolve().then(() => console.log("C"));
console.log("D");Synchroner Code zuerst (A, D), dann Microtasks in FIFO-Reihenfolge (B, C).
console.log("1");
queueMicrotask(() => console.log("2"));
Promise.resolve().then(() => console.log("3"));
console.log("4");queueMicrotask und Promise.then planen beide Microtasks. Sie laufen nach dem sync-Code in Planungsreihenfolge.
class Box {
#value;
constructor(v) { this.#value = v; }
get() { return this.#value; }
}
const b = new Box(42);
console.log(b.get(), b["#value"]);Private Felder (#) sind nur innerhalb des Klassenkörpers zugreifbar; b["#value"] liest eine normale String-Property, die nicht existiert.
class A {
constructor() { this.tag = "A"; }
}
class B extends A {
constructor() {
super();
this.tag = "B";
}
}
console.log(new B().tag);super() führt A's Konstruktor aus (setzt this.tag = "A"); die nächste Zuweisung überschreibt auf "B".
class Animal {
static [Symbol.hasInstance]() { return true; }
}
console.log({} instanceof Animal);instanceof delegiert an Symbol.hasInstance — eine eigene Implementierung kann zurückgeben, was sie will.
const o = Object.create(null);
console.log(Object.getPrototypeOf(o));
console.log(o.toString);Object.create(null) erzeugt ein Objekt ohne Prototyp — es gibt kein Object.prototype.toString zum Erben.
function show() { return [this.tag, ...arguments]; }
console.log(show.call({ tag: "A" }, 1, 2));
console.log(show.apply({ tag: "B" }, [3, 4]));call nimmt Args als Liste; apply als Array. Beide setzen this auf das erste Argument.
const proto = {
get hi() { return `hi ${this.name}`; }
};
const o = Object.create(proto);
o.name = "Ada";
console.log(o.hi);Getter, die am Prototyp definiert sind, laufen mit this gleich dem Receiver — o, das name = "Ada" hat.
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");
}Ein non-configurable Daten-Deskriptor erlaubt weiterhin, value zu ändern (und writable von true auf false umzuschalten). Andere Attribute sind gesperrt.
const o = { a: 1 };
Object.defineProperty(o, "b", { value: 2, enumerable: false });
console.log(Object.keys(o), Object.getOwnPropertyNames(o));Object.keys liefert nur eigene aufzählbare Properties. getOwnPropertyNames liefert alle eigenen Properties mit String-Key.
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"])]);Ein Iterator, der sich selbst aus [Symbol.iterator] zurückgibt, ist auch iterierbar, also kann Spread ihn konsumieren.
async function* nums() {
yield 1; yield 2;
}
const out = [];
for await (const n of nums()) out.push(n);
console.log(out);for-await-of awaitet jeden yield-Wert, also sammelt out die ausgepackten Zahlen.
const safe = new Proxy({ x: 1 }, {
has(t, p) { return p === "x"; }
});
console.log("x" in safe, "y" in safe);Die has-Trap fängt den in-Operator ab. Wir erlauben nur "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 ist flach — das oberste x ist gesperrt, aber das verschachtelte Objekt bleibt veränderlich.
class A {}
A.prototype.constructor = function() { return { tag: "fake" }; };
console.log(new A().tag);new A() ruft A direkt auf (den ursprünglichen Klassenkonstruktor), nicht die constructor-Property am Prototyp.
async function f() { return 42; }
const r = f();
console.log(r);Eine async-Funktion aufzurufen liefert eine Promise. Da die Funktion synchron returnt, ist die Promise bereits mit 42 fulfilled (gedruckt als 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 rejected beim ersten Rejection — der catch feuert mit dieser Reason und die erfüllten Werte werden verworfen.
async function main() {
console.log("a");
await Promise.resolve();
console.log("b");
}
main();
console.log("c");Zuerst der synchrone Code in main ('a'), dann gibt await die Kontrolle zurück. 'c' wird ausgegeben, dann setzt die Microtask main fort und gibt 'b' aus.
Promise.allSettled([
Promise.resolve(1),
Promise.reject("x"),
]).then((arr) => console.log(arr.map(r => r.status)));Promise.allSettled löst mit einem Array von { status, value | reason } auf. Status ist 'fulfilled' oder 'rejected'.
Nach jedem Macrotask leert die Engine die Microtask-Queue vor dem nächsten Macrotask oder Rendering. setTimeout(0) ist ein Macrotask und wartet mindestens einen Zyklus.
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 löst mit dem Input auf, der zuerst settled — b settled nach 10ms, a nach 50ms.
async function f() { return 5; }
const r = f();
console.log(r instanceof Promise, await r);Eine async-Funktion liefert immer eine Promise — selbst wenn der Body einen einfachen Wert returnt, wird er verpackt.
async function f() {
try {
await Promise.reject(new Error("boom"));
} catch (e) {
return "caught:" + e.message;
}
}
console.log(await f());await auf eine rejected Promise wirft synchron innerhalb der async-Funktion — try/catch fängt es.
async function f() {
throw new Error("x");
}
try {
f();
console.log("after call");
} catch (e) {
console.log("caught");
}Eine async-Funktion, die ein Rejection zurückgibt, ist KEIN synchroner Throw — ohne await wird der Fehler zur 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 rejected, sobald irgendein Input rejected — die Rejection schließt das Warten kurz.
try {
await Promise.any([
Promise.reject("a"),
Promise.reject("b"),
]);
} catch (e) {
console.log(e.constructor.name, e.errors);
}Promise.any rejected mit einem AggregateError, der alle Reasons enthält, nur wenn alle Inputs rejecten.
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("sync");Sync zuerst, dann Microtasks (Promise.then), dann Macrotasks (setTimeout).
const r = await Promise.resolve(1)
.then(v => v + 1)
.then(v => v * 10);
console.log(r);Jedes .then sieht den vorherigen Rückgabewert: 1 → 2 → 20.
async function getX() { return 5; }
async function run() {
const x = getX();
console.log(typeof x.then, x + 1);
}
await run();Vergessenes await belässt x als Promise. x + 1 konvertiert 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 innerhalb von for…of läuft sequentiell. Jede Iteration wartet auf die vorige Promise — out erhält die ausgepackten Werte.
async function run() {
const items = [1, 2, 3];
return Promise.all(items.map(async n => n * 2));
}
console.log(await run());Map+async startet die async-Funktionen parallel, jede gibt eine Promise zurück. Promise.all packt alle aus.
const out = await Promise.allSettled([
Promise.resolve(1),
Promise.reject("x"),
]);
console.log(out.map(r => r.status));Promise.allSettled rejected nie — sie liefert Objekte der Form { 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);Alle drei Delays laufen parallel, also entspricht die Gesamtzeit ungefähr dem längsten (~50ms), nicht 150ms.
try {
await Promise.resolve(1).then(v => { throw new Error("oops"); });
} catch (e) {
console.log("caught:" + e.message);
}Ein throw in einem .then-Handler verwandelt die Chain in eine Rejection, die await wieder in einen sync-Throw umwandelt.
async function* gen() { yield 1; yield 2; yield 3; }
const out = [];
for await (const n of gen()) out.push(n);
console.log(out);Ein async-Generator yielded promise-verpackte Werte; for-await-of awaitet jeden einzeln.
console.log("before");
const p = new Promise((res) => {
console.log("executor");
res();
});
console.log("after");
await p;
console.log("done");Der Promise-Executor läuft synchron, wenn die Promise konstruiert wird.
const thenable = { then(res) { res("hi"); } };
const out = await thenable;
console.log(out);await akzeptiert jedes Thenable. Die Engine ruft .then(resolve, reject) auf und nutzt den resolved Wert.
// Imagine: window.addEventListener('unhandledrejection', e => console.log('unhandled'));
Promise.reject(new Error("boom"));Eine Rejection ohne .catch / await löst das unhandledrejection-Event auf window (oder process in Node) aus.
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);Den Resolver einzufangen erlaubt, eine Promise außerhalb des Executors zu settlen — das klassische Deferred-Pattern.
let a;
queueMicrotask(() => a = "qm");
Promise.resolve().then(() => a = "p");
await Promise.resolve();
console.log(a);Beide planen Microtasks. Sie laufen FIFO: queueMicrotask setzt zuerst "qm", dann überschreibt der .then-Callback mit "p".
const user = { name: "Ada", profile: null };
console.log(user?.profile?.email ?? "n/a");Optional Chaining schließt bei profile (null) kurz und liefert undefined. ?? fällt auf 'n/a' zurück, weil die linke Seite nullish ist.
console.log(0 ?? "fallback");
console.log("" ?? "fallback");
console.log(null ?? "fallback");?? fällt nur bei null oder undefined zurück. 0 und "" bleiben unverändert (anders als ||, das sie ersetzen würde).
const fn = null;
console.log(fn?.());Optional Call (?.()) schließt bei null/undefined-Callee zu undefined kurz statt TypeError zu werfen.
const entries = [["a", 1], ["b", 2]];
const obj = Object.fromEntries(entries);
console.log(obj.a, obj.b);Object.fromEntries kehrt Object.entries um — es baut ein Objekt aus Key/Value-Paaren.
const a = { x: { y: 1 } };
const b = structuredClone(a);
b.x.y = 99;
console.log(a.x.y);structuredClone erzeugt eine tiefe Kopie. Mutieren von b.x.y wirkt sich nicht auf a aus, also bleibt a.x.y bei 1. In modernem Node und Browsern ohne Polyfills verfügbar.
const obj = { greet: null };
console.log(obj.greet?.());
console.log(obj.missing?.());?.() schließt zu undefined kurz, wenn der Wert davor null oder undefined ist — kein Fehler.
const arr = [1];
const [a, b = 99, c = 100] = arr;
console.log(a, b, c);Array-Destructuring-Defaults greifen bei undefined — sowohl b als auch c bekommen ihre Defaults.
const m = new Map([["a", 1], ["b", 2]]);
const o = Object.fromEntries(m);
console.log(o.a, o.b);Object.fromEntries akzeptiert jedes Iterable von [Key, Value]-Paaren — auch Maps.
let a = "";
let b = "value";
a ||= "fallback";
b ||= "fallback";
console.log(a, b);||= weist nur zu, wenn die linke Seite falsy ist. "" ist falsy → a wird "fallback"; "value" ist truthy → b bleibt unverändert.
let a = 0;
let b = null;
a ??= 99;
b ??= 99;
console.log(a, b);??= weist nur zu, wenn die linke Seite null oder undefined ist. 0 bleibt erhalten; null wird ersetzt.
const big = 1_000_000;
console.log(big === 1000000);Numerische Separatoren (_) sind syntaktischer Zucker — sie werden beim Parsen entfernt und beeinflussen den Wert nicht.
const a = 9007199254740993n;
const b = a + 1n;
console.log(b);BigInt verarbeitet beliebig große Integer exakt. Das nachgestellte 'n' markiert ein BigInt-Literal.
const arr = [10, 20, 30];
console.log(arr.at(-1), arr.at(-2));Array.prototype.at unterstützt negative Indizes, vom Ende aus gezählt.
console.log("a-b-c".replaceAll("-", "/"));String.prototype.replaceAll ersetzt jeden Match, ohne ein globales Regex zu brauchen.
const o = { x: 1 };
console.log(Object.hasOwn(o, "x"), Object.hasOwn(o, "toString"));Object.hasOwn prüft nur eigene Properties — "toString" wird von Object.prototype geerbt.
console.log([1, [2, [3, [4]]]].flat(2));flat(depth) flacht bis zu depth Ebenen ab. flat(2) lässt das tiefste [4] unangetastet. Nutze flat(Infinity) für komplette Abflachung.
console.log([1, 2, 3].flatMap(n => [n, n * 10]));flatMap ist map gefolgt von flat(1) — praktisch für Eins-zu-viele-Transformationen.
const winner = await Promise.any([
Promise.reject("a"),
Promise.resolve("b"),
Promise.resolve("c"),
]);
console.log(winner);Promise.any löst mit dem ersten erfüllten Wert auf und ignoriert Rejections.
globalThis.__token = 42;
console.log(globalThis.__token);globalThis ist der standardisierte Zugriff auf das globale Objekt — funktioniert in Browser, Node, Workern und Deno gleichermaßen.
// In an ES module:
// const data = await fetch("/api").then(r => r.json());
console.log("module loaded after data");Top-level await pausiert die Modulauswertung. Importierende Module warten, bevor ihr eigener Body läuft.
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) gruppiert Elemente anhand des Callback-Rückgabewerts und liefert ein einfaches Objekt mit Gruppen-Keys.
let target = { name: "Ada" };
const ref = new WeakRef(target);
console.log(ref.deref()?.name);WeakRef.deref() liefert das Target, solange es erreichbar ist. Sobald der GC es einsammelt, liefert deref() 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 bewahrt eingebaute Typen wie Date, Map, Set, RegExp, ArrayBuffer — nicht nur einfache Objekte.
console.log([1, 2, 3, 4].findLast(n => n < 3));findLast scannt von rechts nach links und liefert das letzte passende Element — hier 2 (der letzte Wert < 3).
function sum(a, b, c) { return a + b + c; }
const args = [1, 2, 3];
console.log(sum(...args));Spread am Aufrufort entfaltet das Iterable in einzelne Argumente — a=1, b=2, c=3.