Co się stanie po uruchomieniu tego kodu?
let a = 1;
const b = 2;
a = 3;
b = 4;
console.log(a, b);let pozwala na ponowne przypisanie, const nie. Próba przypisania do b rzuca TypeError zanim console.log się wykona.
Każde pytanie JavaScript z poprawną odpowiedzią i jasnym wyjaśnieniem.
let a = 1;
const b = 2;
a = 3;
b = 4;
console.log(a, b);let pozwala na ponowne przypisanie, const nie. Próba przypisania do b rzuca TypeError zanim console.log się wykona.
console.log(0 == "0");
console.log(0 === "0");Luźna równość (==) wymusza konwersję typów, więc 0 == "0" to true. Ścisła (===) nie konwertuje, więc 0 === "0" to false.
const arr = [1, 2, 3];
arr[10] = 99;
console.log(arr.length);Przypisanie do indeksu 10 rozszerza tablicę. Długość to 11; indeksy 3–9 są puste (sparse).
const s = "hello";
console.log(s[0], s.length);Stringi obsługują dostęp przez indeks (s[0] = 'h') i mają property length. "hello" ma 5 znaków.
const obj = { name: "Ada" };
const { name, age = 30 } = obj;
console.log(name, age);Destrukturyzacja z wartością domyślną używa jej tylko gdy property jest undefined. age nie ma w obj, więc dostaje 30.
console.log(typeof null);typeof null zwraca "object" — wieloletnia osobliwość JS zachowana dla kompatybilności.
console.log(NaN === NaN);
console.log(Number.isNaN(NaN));NaN to jedyna wartość nierówna sama sobie. Do wykrycia służy Number.isNaN (albo Object.is).
console.log(null == undefined);
console.log(null === undefined);
console.log(null == 0);== uznaje null i undefined za równe sobie, ale nie czemuś innemu (nie konwertuje na 0).
const a = [1, 2, 3, 4, 5];
a.length = 2;
console.log(a);Przypisanie mniejszej wartości do length skraca tablicę — nadmiarowe elementy są całkowicie usuwane.
const a = new Array(3);
const b = Array.of(3);
console.log(a.length, b.length);new Array(3) traktuje pojedynczą liczbę jako długość (3 puste sloty). Array.of(3) jako pojedynczy element.
let s = "hello";
s[0] = "H";
console.log(s);Stringi są niezmienne. Przypisanie po cichu się nie udaje (w strict mode rzuca) — string pozostaje "hello".
const x = 10;
console.log(`x is ${x > 5 ? "big" : "small"}`);Template literal wykonuje dowolne wyrażenie w ${...}, łącznie z operatorem ternarnym.
const o = { 2: "a", 1: "b", "x": "c" };
console.log(Object.keys(o));Klucze przypominające liczby całkowite są pierwsze, posortowane rosnąco; potem klucze stringowe w kolejności wstawiania.
function fn(...args) { return args.length; }
const arr = [1, 2, 3];
console.log(fn(...arr));Spread (...arr) w miejscu wywołania przekazuje elementy jako osobne argumenty; rest (...args) zbiera je w funkcji.
const x = 5;
const label = x > 0 ? "+" : x < 0 ? "-" : "0";
console.log(label);Zagnieżdżone operatory ternarne ewaluują się od prawej. x > 0 jest true, więc wygrywa pierwszy branch: "+".
const a = 0 || "fallback";
const b = "" || "fallback";
const c = "value" || "fallback";
console.log(a, b, c);|| zwraca pierwszy truthy operand lub ostatni. 0 i "" są falsy, więc dla a i b wygrywa fallback.
const a = 0 ?? "fallback";
const b = null ?? "fallback";
const c = undefined ?? "fallback";
console.log(a, b, c);?? aktywuje fallback tylko dla null lub undefined — 0 zostaje zachowane (inaczej niż ||).
console.log(Boolean(""), Boolean("0"), Boolean([]), Boolean({}));Wartości falsy: "", 0, NaN, null, undefined, false. Niepusty string ORAZ dowolny obiekt (także [] i {}) są truthy.
console.log(1 + "2" + 3);
console.log(1 + 2 + "3");+ jest lewostronnie łączny. Gdy pojawi się string, reszta się skleja. "1"+"2"+3 → "123"; 1+2 a potem +"3" → "33".
console.log(parseInt("08"));
console.log(parseInt("08", 10));Współczesny parseInt domyślnie używa radix 10 dla nie-0x stringów (stary domyślny system ósemkowy dla wiodącego 0 wycofano w ES5+). Mimo to dla jasności zawsze podawaj radix.
const a = Array.from({ length: 3 }, (_, i) => i * 2);
console.log(a);Array.from z obiektem zawierającym tylko length plus funkcja mapująca to standardowy sposób budowania tablicy N obliczonych wartości.
const a = [1, NaN, 3];
console.log(a.indexOf(NaN), a.includes(NaN));indexOf używa === (NaN nigdy nie jest równe sobie). includes używa SameValueZero, który uznaje NaN za równe NaN.
const name = "Ada", age = 36;
const user = { name, age, role: "admin" };
console.log(user.name, user.role);Skrót property { name } jest równoważny { name: name } — używa wartości zmiennej.
const o = { x: 1 };
o.x = 2;
o.y = 3;
console.log(o.x, o.y);const chroni powiązanie (nie można ponownie przypisać o), ale sam obiekt nadal można modyfikować.
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 iteruje po indeksowanych wartościach tablicy (3). for...in chodzi po wszystkich wyliczalnych kluczach string, w tym dodanym "extra" (4).
function makeCounter() {
let n = 0;
return () => ++n;
}
const c = makeCounter();
c(); c();
console.log(c());Closure zamyka n. Każde wywołanie zwiększa licznik; trzecie wywołanie zwraca 3.
console.log(typeof a);
var a = 1;
console.log(typeof b);Deklaracje var są hoistowane (zainicjalizowane jako undefined). typeof na niezadeklarowanej zmiennej b zwraca 'undefined' bez błędu.
const obj = {
name: "Ada",
greet() { return `hi ${this.name}`; }
};
const g = obj.greet;
console.log(g());Odłączona metoda traci this. W module / strict mode this to undefined; w przeglądarce non-strict to window, gdzie window.name domyślnie jest '' — w nowoczesnych środowiskach zazwyczaj 'hi undefined'.
const a = [1, 2];
const b = [3, 4];
const c = [...a, ...b, 5];
console.log(c.length, c[2]);Spread rozkłada obie tablice do c i dokleja 5: [1, 2, 3, 4, 5]. Długość 5, indeks 2 to 3.
function f(a, b = 2, ...rest) {
return [a, b, rest];
}
console.log(f(1, undefined, 3, 4));Wartości domyślne aktywują się przy undefined, więc b to 2. Rest parameter zbiera pozostałe argumenty w tablicę.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}var jest function-scoped — wszystkie callbacki zamykają się nad tym samym i, które ma już 3 gdy się uruchamiają.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}let tworzy nowe powiązanie w każdej iteracji, więc każdy callback zamyka się nad swoim i.
const obj = {
name: "Ada",
greet: () => `hi ${this?.name}`,
};
console.log(obj.greet());Funkcje strzałkowe nie wiążą własnego this — zostaje to z otaczającego scope'a (tu top-level → undefined lub globalThis).
class Counter {
n = 0;
inc() { this.n++; }
}
const c = new Counter();
const f = c.inc;
f();
console.log(c.n);Ciało klasy domyślnie działa w strict mode. Odłączone f() ma this === undefined, więc this.n++ rzuca TypeError.
const a = { x: { y: 1 } };
const b = { ...a };
b.x.y = 99;
console.log(a.x.y);Spread to płytka kopia — zagnieżdżone obiekty są dzielone przez referencję. Do głębokiej kopii użyj structuredClone.
console.log(typeof foo);
console.log(typeof bar);
function foo() {}
var bar = function() {};Deklaracje funkcji są hoistowane wraz z ciałem. Function expressions przypisane do var hoistują tylko powiązanie (na początku undefined).
const x = (function() {
return 42;
})();
console.log(x);IIFE — zdefiniowana i natychmiast wywołana. Końcowe () wywołuje funkcję, więc x dostaje wartość zwracaną.
function add(a, b) { return a + b; }
const add5 = add.bind(null, 5);
console.log(add5(3));bind częściowo aplikuje argumenty po this. add5 ma a ustawione na 5, więc add5(3) liczy 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);Setter podwaja wartość przed zapisem (this._x = 10). Getter zwraca ją bez zmian.
const data = { user: { profile: { age: 25 } } };
const { user: { profile: { age } } } = data;
console.log(age);Zagnieżdżona destrukturyzacja podąża za strukturą obiektu — tylko age jest powiązane jako zmienna.
let calls = 0;
function id() { calls++; return calls; }
function f(x = id()) { return x; }
f(); f(10); f();
console.log(calls);Wyrażenia parametrów domyślnych ewaluują się tylko gdy argument to undefined. f(10) pomija default, więc id() uruchamia się tylko dwa razy.
console.log([10, 1, 2].sort());Domyślne sortowanie jest leksykograficzne — elementy są porównywane jako stringi. Dla numerycznego użyj .sort((a,b) => a-b).
const a = { x: 1, y: 2 };
const b = { ...a, x: 99 };
console.log(b);Późniejsze właściwości nadpisują wcześniejsze. Kolejność ma znaczenie: { x: 99, ...a } pozwoliłoby a.x wygrać.
const u = { name: "Ada", contact: null };
const email = u?.contact?.email ?? "n/a";
console.log(email);?. krótko-spina się na null/undefined, zwracając undefined; ?? aktywuje fallback "n/a".
const fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns[0](), fns[1](), fns[2]());let tworzy nowe powiązanie w każdej iteracji, więc każde domknięcie łapie swoje 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 łapane jest przez closure (nie this), więc this w wewnętrznej funkcji nie ma znaczenia — suma to 6.
function head(first, ...rest) { return [first, rest.length]; }
console.log(head("a", "b", "c", "d"));rest zbiera wszystko po first do tablicy o długości 3.
const a = Symbol("id");
const b = Symbol("id");
console.log(a === b, a.description === b.description);Każde wywołanie Symbol() daje unikalną wartość. Opcjonalna description to tylko etykieta i nie wpływa na równość.
const m = new Map();
m.set(2, "a"); m.set(1, "b"); m.set("x", "c");
console.log([...m.keys()]);Map zachowuje kolejność wstawiania dla dowolnego typu klucza — w odróżnieniu od zwykłego obiektu, gdzie klucze przypominające liczby całkowite są sortowane rosnąco.
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");Najpierw kod synchroniczny (1, 4). Potem mikrotaski (Promise.then → 3). Na końcu makrotaski (setTimeout → 2).
function* gen() {
yield 1;
yield 2;
return 3;
}
const g = gen();
console.log(g.next().value, g.next().value, g.next().value);Każde next() zwraca { value, done }. Trzecie wywołanie trafia w return, wystawiając 3 z done=true.
class A {
greet() { return "A"; }
}
class B extends A {
greet() { return "B" + super.greet(); }
}
console.log(new B().greet());B.greet zwraca 'B' połączone z super.greet() (czyli 'A' z A), dając '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);Pułapka get Proxy zwraca prawdziwą wartość gdy property istnieje, inaczej 0. p.x → 1, p.y → 0 (y nie ma w target).
const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));Zwykłe obiekty dziedziczą z Object.prototype. Sam Object.prototype jest na szczycie łańcucha, więc jego prototyp to 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 emituje wartości; return to wartość końcowa (z done=true). Potem .next() daje { 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);Pułapka get przechwytuje odczyt właściwości. Istniejące przechodzą; brakujące zwracają "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]);Implementacja [Symbol.iterator] sprawia, że obiekt jest iterowalny — spread go wywołuje i zbiera wartości aż do done = true.
const o = { x: 1 };
console.log(Reflect.get(o, "x"), Reflect.has(o, "y"));Reflect odzwierciedla operatory — Reflect.get działa jak obj[p]; Reflect.has jak "p" in obj.
const wm = new WeakMap();
let key = {};
wm.set(key, "hello");
console.log(wm.get(key));
key = null;
console.log(wm.has({}));Klucze WeakMap to obiekty porównywane przez referencję. {} to nowy obiekt — nie ten sam co oryginał — więc .has zwraca false.
console.log("A");
Promise.resolve().then(() => console.log("B"));
Promise.resolve().then(() => console.log("C"));
console.log("D");Najpierw wykonuje się kod synchroniczny (A, D), potem microtaski w kolejności FIFO (B, C).
console.log("1");
queueMicrotask(() => console.log("2"));
Promise.resolve().then(() => console.log("3"));
console.log("4");queueMicrotask i Promise.then schedule'ują microtaski. Uruchamiają się po kodzie sync, w kolejności zaplanowania.
class Box {
#value;
constructor(v) { this.#value = v; }
get() { return this.#value; }
}
const b = new Box(42);
console.log(b.get(), b["#value"]);Prywatne pola (#) są dostępne tylko wewnątrz klasy; b["#value"] odczytuje zwykłą właściwość string, której nie ma.
class A {
constructor() { this.tag = "A"; }
}
class B extends A {
constructor() {
super();
this.tag = "B";
}
}
console.log(new B().tag);super() uruchamia konstruktor A (ustawia this.tag = "A"); kolejne przypisanie nadpisuje na "B".
class Animal {
static [Symbol.hasInstance]() { return true; }
}
console.log({} instanceof Animal);instanceof deleguje do Symbol.hasInstance — własna implementacja może zwrócić dowolną wartość.
const o = Object.create(null);
console.log(Object.getPrototypeOf(o));
console.log(o.toString);Object.create(null) tworzy obiekt bez prototypu — nie ma żadnego Object.prototype.toString do odziedziczenia.
function show() { return [this.tag, ...arguments]; }
console.log(show.call({ tag: "A" }, 1, 2));
console.log(show.apply({ tag: "B" }, [3, 4]));call przyjmuje argumenty jako listę; apply jako tablicę. Oba ustawiają this na pierwszy argument.
const proto = {
get hi() { return `hi ${this.name}`; }
};
const o = Object.create(proto);
o.name = "Ada";
console.log(o.hi);Gettery zdefiniowane na prototypie działają z this ustawionym na receiver — o, gdzie 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");
}Niemodyfikowalny deskryptor danych nadal pozwala zmienić value (i przełączyć writable z true na false). Inne atrybuty są zablokowane.
const o = { a: 1 };
Object.defineProperty(o, "b", { value: 2, enumerable: false });
console.log(Object.keys(o), Object.getOwnPropertyNames(o));Object.keys zwraca tylko własne wyliczalne właściwości. getOwnPropertyNames zwraca wszystkie własne właściwości z kluczem 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"])]);Iterator, który z [Symbol.iterator] zwraca samego siebie, jest jednocześnie iterowalny — spread może go skonsumować.
async function* nums() {
yield 1; yield 2;
}
const out = [];
for await (const n of nums()) out.push(n);
console.log(out);for-await-of awaituje każdą wyrzuconą wartość, więc out zbiera już rozpakowane liczby.
const safe = new Proxy({ x: 1 }, {
has(t, p) { return p === "x"; }
});
console.log("x" in safe, "y" in safe);Pułapka has przechwytuje operator in. Pozwalamy tylko na "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 jest płytkie — top-level x jest zablokowane, ale zagnieżdżony obiekt nadal można modyfikować.
class A {}
A.prototype.constructor = function() { return { tag: "fake" }; };
console.log(new A().tag);new A() wywołuje bezpośrednio A (oryginalny konstruktor klasy), a nie property constructor z prototypu.
async function f() { return 42; }
const r = f();
console.log(r);Wywołanie async funkcji zwraca Promise. Funkcja kończy się synchronicznie, więc Promise jest już spełniony wartością 42 (drukowane jako 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 odrzuca przy pierwszym rejection — catch dostaje powód, a spełnione wartości są pomijane.
async function main() {
console.log("a");
await Promise.resolve();
console.log("b");
}
main();
console.log("c");Synchroniczny kod w main najpierw ('a'), potem await oddaje kontrolę. 'c' drukuje się dalej, mikrotask wraca do main i drukuje 'b'.
Promise.allSettled([
Promise.resolve(1),
Promise.reject("x"),
]).then((arr) => console.log(arr.map(r => r.status)));Promise.allSettled rozwiązuje się tablicą { status, value | reason }. Status to 'fulfilled' lub 'rejected'.
Po każdym macrotasku silnik opróżnia kolejkę microtasków zanim przejdzie do kolejnego macrotaska lub renderu. setTimeout(0) to macrotask, który czeka co najmniej jeden cykl.
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 rozwiązuje się z pierwszym osiadłym promise'em — b osiada po 10ms, a po 50ms.
async function f() { return 5; }
const r = f();
console.log(r instanceof Promise, await r);Async function zawsze zwraca Promise — nawet gdy ciało zwraca zwykłą wartość, zostaje opakowana.
async function f() {
try {
await Promise.reject(new Error("boom"));
} catch (e) {
return "caught:" + e.message;
}
}
console.log(await f());await na rejected promise rzuca synchronicznie wewnątrz async function — try/catch to łapie.
async function f() {
throw new Error("x");
}
try {
f();
console.log("after call");
} catch (e) {
console.log("caught");
}Async function zwracająca rejection to NIE synchroniczny throw — bez await błąd staje się 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 odrzuca się gdy tylko któryś z inputów zostanie odrzucony — rejekcja krótko-spina oczekiwanie.
try {
await Promise.any([
Promise.reject("a"),
Promise.reject("b"),
]);
} catch (e) {
console.log(e.constructor.name, e.errors);
}Promise.any odrzuca się AggregateError z wszystkimi powodami odrzucenia, ale tylko gdy wszystkie inputy są odrzucone.
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("sync");Najpierw kod sync, potem microtaski (Promise.then), potem macrotaski (setTimeout).
const r = await Promise.resolve(1)
.then(v => v + 1)
.then(v => v * 10);
console.log(r);Każde .then dostaje poprzednią wartość zwracaną: 1 → 2 → 20.
async function getX() { return 5; }
async function run() {
const x = getX();
console.log(typeof x.then, x + 1);
}
await run();Zapomniany await pozostawia x jako Promise. x + 1 konwertuje przez 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 w for…of działa sekwencyjnie. Każda iteracja czeka na poprzedni promise — out dostaje rozpakowane wartości.
async function run() {
const items = [1, 2, 3];
return Promise.all(items.map(async n => n * 2));
}
console.log(await run());Map+async odpala async funkcje równolegle, każda zwraca Promise. Promise.all rozpakowuje wszystkie.
const out = await Promise.allSettled([
Promise.resolve(1),
Promise.reject("x"),
]);
console.log(out.map(r => r.status));Promise.allSettled nigdy się nie odrzuca — zwraca obiekty { 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);Wszystkie trzy delay'e działają równolegle, więc łączny czas to mniej więcej najdłuższy pojedynczy (~50ms), a nie 150ms.
try {
await Promise.resolve(1).then(v => { throw new Error("oops"); });
} catch (e) {
console.log("caught:" + e.message);
}Throw w handlerze .then zamienia łańcuch na rejekcję, którą await ponownie zamienia na synchroniczny throw.
async function* gen() { yield 1; yield 2; yield 3; }
const out = [];
for await (const n of gen()) out.push(n);
console.log(out);Async generator wyrzuca wartości opakowane w promise; for-await-of czeka na każdą.
console.log("before");
const p = new Promise((res) => {
console.log("executor");
res();
});
console.log("after");
await p;
console.log("done");Executor Promise'a uruchamia się synchronicznie w momencie konstrukcji Promise'a.
const thenable = { then(res) { res("hi"); } };
const out = await thenable;
console.log(out);await akceptuje dowolnego thenable'a. Silnik wywołuje .then(resolve, reject) i używa rozwiązanej wartości.
// Imagine: window.addEventListener('unhandledrejection', e => console.log('unhandled'));
Promise.reject(new Error("boom"));Rejekcja bez .catch / await odpala zdarzenie unhandledrejection na window (lub process w 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);Zachowanie resolvera pozwala rozwiązać Promise spoza executora — klasyczny wzorzec deferred.
let a;
queueMicrotask(() => a = "qm");
Promise.resolve().then(() => a = "p");
await Promise.resolve();
console.log(a);Oba schedule'ują microtaski. Działają w kolejności FIFO: queueMicrotask ustawia "qm", potem callback .then nadpisuje na "p".
const user = { name: "Ada", profile: null };
console.log(user?.profile?.email ?? "n/a");Optional chaining przerywa na profile (null) zwracając undefined. ?? wraca do 'n/a' bo lewa strona jest nullish.
console.log(0 ?? "fallback");
console.log("" ?? "fallback");
console.log(null ?? "fallback");?? używa fallbacku tylko gdy lewa strona to null lub undefined. 0 i "" przechodzą bez zmian (inaczej niż ||).
const fn = null;
console.log(fn?.());Wywołanie opcjonalne (?.()) zwraca undefined gdy wywoływane jest null/undefined, zamiast rzucać TypeError.
const entries = [["a", 1], ["b", 2]];
const obj = Object.fromEntries(entries);
console.log(obj.a, obj.b);Object.fromEntries jest odwrotnością Object.entries — buduje obiekt z par klucz/wartość.
const a = { x: { y: 1 } };
const b = structuredClone(a);
b.x.y = 99;
console.log(a.x.y);structuredClone tworzy głęboką kopię. Zmiana b.x.y nie wpływa na a, więc a.x.y to nadal 1. Dostępne w nowoczesnym Node i przeglądarkach bez polyfilli.
const obj = { greet: null };
console.log(obj.greet?.());
console.log(obj.missing?.());?.() krótko-spina się do undefined gdy wartość przed nim to null lub undefined — bez rzucania błędu.
const arr = [1];
const [a, b = 99, c = 100] = arr;
console.log(a, b, c);Wartości domyślne w destrukturyzacji tablicy aktywują się na undefined — b i c dostają swoje domyślne.
const m = new Map([["a", 1], ["b", 2]]);
const o = Object.fromEntries(m);
console.log(o.a, o.b);Object.fromEntries akceptuje dowolny iterowalny zbiór par [klucz, wartość] — w tym Mapy.
let a = "";
let b = "value";
a ||= "fallback";
b ||= "fallback";
console.log(a, b);||= przypisuje tylko gdy lewa strona jest falsy. "" jest falsy → a staje się "fallback"; "value" jest truthy → b bez zmian.
let a = 0;
let b = null;
a ??= 99;
b ??= 99;
console.log(a, b);??= przypisuje tylko gdy lewa strona to null lub undefined. 0 zostaje; null zostaje zastąpione.
const big = 1_000_000;
console.log(big === 1000000);Separatory liczbowe (_) to cukier składniowy — są usuwane na etapie parsowania i nie wpływają na wartość.
const a = 9007199254740993n;
const b = a + 1n;
console.log(b);BigInt obsługuje dokładnie liczby całkowite o dowolnym rozmiarze. Końcowe 'n' oznacza literał BigInt.
const arr = [10, 20, 30];
console.log(arr.at(-1), arr.at(-2));Array.prototype.at obsługuje ujemne indeksy, licząc od końca.
console.log("a-b-c".replaceAll("-", "/"));String.prototype.replaceAll zamienia każde wystąpienie bez potrzeby globalnego regexa.
const o = { x: 1 };
console.log(Object.hasOwn(o, "x"), Object.hasOwn(o, "toString"));Object.hasOwn sprawdza tylko własne właściwości — "toString" jest dziedziczone z Object.prototype.
console.log([1, [2, [3, [4]]]].flat(2));flat(depth) spłaszcza do depth poziomów. flat(2) pozostawia najgłębsze [4]. Użyj flat(Infinity) dla pełnego spłaszczenia.
console.log([1, 2, 3].flatMap(n => [n, n * 10]));flatMap to map plus flat(1) — przydatne do transformacji jeden-do-wielu.
const winner = await Promise.any([
Promise.reject("a"),
Promise.resolve("b"),
Promise.resolve("c"),
]);
console.log(winner);Promise.any rozwiązuje się pierwszą fulfilled wartością, ignorując rejekcje.
globalThis.__token = 42;
console.log(globalThis.__token);globalThis to ujednolicony uchwyt na obiekt globalny — działa w przeglądarce, Node, workerach i Deno.
// In an ES module:
// const data = await fetch("/api").then(r => r.json());
console.log("module loaded after data");Top-level await wstrzymuje ewaluację modułu. Moduły importujące czekają na niego, zanim wykonają swoje ciało.
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) grupuje elementy po wartości zwracanej przez callback, zwracając zwykły obiekt z kluczami-grupami.
let target = { name: "Ada" };
const ref = new WeakRef(target);
console.log(ref.deref()?.name);WeakRef.deref() zwraca cel póki jest osiągalny. Gdy GC go zbierze, deref() zwraca 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 zachowuje wbudowane typy takie jak Date, Map, Set, RegExp, ArrayBuffer — nie tylko zwykłe obiekty.
console.log([1, 2, 3, 4].findLast(n => n < 3));findLast skanuje od prawej do lewej i zwraca ostatni pasujący element — tutaj 2 (ostatnia wartość < 3).
function sum(a, b, c) { return a + b + c; }
const args = [1, 2, 3];
console.log(sum(...args));Spread w miejscu wywołania rozwija iterowalny zbiór do osobnych argumentów — a=1, b=2, c=3.