Tenzan Academy
Motyw
  • 8 sekcji
  • 24 pytania
  • 22 fiszki

JavaScript: zaawansowana semantyka i nowoczesne API (ES2023/ES2024)

Kurs semantyki JavaScriptu: koercja typów, hoisting, domknięcia, this, event loop, async/await, prototypy, klasy oraz nowoczesne API ES2023/2024 dla praktykujących inżynierów.

JavaScript: zaawansowana semantyka i nowoczesne API (ES2023/ES2024)

Kurs semantyki JavaScriptu: koercja typów, hoisting, domknięcia, this, event loop, async/await, prototypy, klasy oraz nowoczesne API ES2023/2024 dla praktykujących inżynierów.

Quiz

Równość, koercja typów i podstawy wartości

na egzaminie

Operator == wykonuje niejawną konwersję typów przed porównaniem (abstract equality comparison), podczas gdy === porównuje wartość i typ bez konwersji (strict equality comparison). W kodzie produkcyjnym === jest domyślnym wyborem; == rezerwuje się dla świadomych, udokumentowanych przypadków.

Termin kluczowy

Koercja typów (type coercion): Automatyczna konwersja wartości z jednego typu na inny, którą silnik JS wykonuje np. w porównaniach, operacjach arytmetycznych i konkatenacji stringów. Kierują nią algorytmy ToPrimitive, ToNumber i ToString ze specyfikacji ECMA-262.

Porównanie=====Komentarz
0 == ''truefalse'' konwertowane do 0
null == undefinedtruefalsespecjalny przypadek w spec
NaN == NaNfalsefalseNaN nigdy nie jest równe samemu sobie
[] == falsetruefalse[] → '' → 0, false → 0
'0' == falsetruefalseoba konwertowane do 0
Pułapki == w praktyce javascript
console.log(0 == '');            // true
console.log(null == undefined);  // true
console.log(NaN == NaN);         // false
console.log([] == false);        // true
console.log('0' == false);       // true
console.log(0 === '');           // false
Pułapka

Porównywanie z NaN operatorem == lub === zawsze zwraca false, także dla NaN == NaN. Do sprawdzania NaN używaj Number.isNaN(x), nigdy x == NaN ani globalnego isNaN(x) (który najpierw koercjuje argument do liczby i daje fałszywe pozytywy, np. dla isNaN('foo')).

Termin kluczowy

Object.is(): Metoda porównania wartości bez koercji, zachowująca się jak === z dwoma wyjątkami: Object.is(NaN, NaN) zwraca true, a Object.is(0, -0) zwraca false. Używana głównie wewnętrznie przez silnik oraz w edge-case'ach numerycznych.

Object.is vs === javascript
Object.is(NaN, NaN);   // true
NaN === NaN;            // false
Object.is(0, -0);       // false
0 === -0;               // true
  • ToPrimitive: obiekt konwertowany jest do prymitywu (najpierw valueOf, potem toString, chyba że kontekst preferuje string).
  • ToNumber: string, boolean i null/undefined konwertowane są do liczby wg ściśle zdefiniowanych reguł.
  • Jeśli oba operandy == są tego samego typu, zachowuje się identycznie jak ===.
  • null == undefined jest jedynym przypadkiem, w którym null porównuje się jako równe czemukolwiek innemu niż samo siebie.
Mnemonik

Zapamiętaj: '3 znaki równości = 3 rzeczy sprawdzane: typ i wartość, zero niespodzianek'. Jeśli piszesz ==, dopisz komentarz dlaczego.

typeof - pułapki

typeof null zwraca 'object' (błąd historyczny w JS z 1995 roku, niemożliwy do naprawienia bez złamania zgodności wstecznej). Dla function typeof zwraca 'function', mimo że funkcje są obiektami. Dla niezadeklarowanej zmiennej typeof nie rzuca błędu, zwraca 'undefined'.

Dalej: 02 Hoisting i Temporal Dead Zone

Hoisting i Temporal Dead Zone

na egzaminie

Hoisting to mechanizm, w którym deklaracje zmiennych i funkcji są rejestrowane na górze swojego zakresu podczas fazy tworzenia środowiska wykonania, przed wykonaniem kodu.

Fakt na test

Przy hoistingu deklaracje var, let, const i function zachowują się zupełnie inaczej. To częste źródło błędów w code review.

Termin kluczowy

Hoisting: Faza tworzenia środowiska wykonania (creation phase), w której silnik rejestruje wszystkie deklaracje w bieżącym zakresie, zanim wykona jakikolwiek kod.

  • Function declarations są hoistowane wraz z całym ciałem funkcji
  • var jest hoistowane z wartością początkową undefined
  • let/const są hoistowane, ale pozostają w Temporal Dead Zone aż do miejsca deklaracji
Termin kluczowy

Temporal Dead Zone (TDZ): Okres między wejściem w zakres blokowy a wykonaniem deklaracji let/const, w którym zmienna istnieje, ale jej odczyt lub zapis rzuca ReferenceError. TDZ wymusza deklarację przed użyciem i wyłapuje błędy kolejności.

DeklaracjaHoistowana?Wartość przed deklaracjąBłąd przy dostępie
var xtakundefinedbrak
let x / const xtak (do TDZ)brak dostępuReferenceError
function f(){}tak, w całościcała funkcja dostępnabrak
class C {}tak (do TDZ)brak dostępuReferenceError
TDZ w praktyce javascript
console.log(typeof aVar);   // 'undefined' - var jest hoistowane
console.log(typeof aLet);   // ReferenceError: Cannot access 'aLet' before initialization

var aVar = 1;
let aLet = 2;
Pułapka

Częsty błąd: założenie, że let/const 'nie są hoistowane w ogóle'. W rzeczywistości są hoistowane identycznie jak var. Silnik blokuje jednak dostęp do nich aż do linii deklaracji (TDZ), co objawia się jako ReferenceError, a nie undefined.

Function declaration vs function expression javascript
console.log(declared());  // działa: 'hoisted'
console.log(expr());      // TypeError: expr is not a function

function declared() { return 'hoisted'; }
var expr = function() { return 'not hoisted'; };
  1. Silnik skanuje zakres i rejestruje wszystkie deklaracje function, var, let, const, class.
  2. Function declarations otrzymują od razu pełną wartość (referencję do funkcji).
  3. var otrzymuje wartość undefined.
  4. let, const i class trafiają do TDZ, bez przypisanej wartości.
  5. Wykonanie kodu liniowego rozpoczyna się, TDZ kończy się w linii z deklaracją.
Mnemonik

TDZ = 'zadeklarowane, ale zablokowane'. var = 'zadeklarowane i puste (undefined)'. function = 'zadeklarowane i gotowe'.

Class hoisting

Deklaracje class są hoistowane tak jak let/const: trafiają do TDZ. Próba użycia klasy przed jej deklaracją rzuca ReferenceError, w przeciwieństwie do starszych function-based konstruktorów, które można było wywołać przed deklaracją dzięki pełnemu hoistingowi function.

Dalej: 03 Domknięcia (closures) i zakres leksykalny

Domknięcia (closures) i zakres leksykalny

na egzaminie

Domknięcie (closure) powstaje, gdy funkcja 'zapamiętuje' zmienne ze swojego zakresu leksykalnego (miejsca zdefiniowania), nawet po tym, jak zakres zewnętrzny zakończył wykonanie. To fundament modułów, memoizacji, prywatnego stanu i większości wzorców funkcyjnych w JS.

Termin kluczowy

Domknięcie (closure): Kombinacja funkcji wraz z referencjami do jej otaczającego stanu leksykalnego (lexical environment). Silnik JS utrzymuje przy życiu zmienne z zewnętrznego zakresu tak długo, jak długo istnieje jakiekolwiek domknięcie, które się do nich odwołuje.

Klasyczny licznik przez domknięcie javascript
function createCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    value: () => count,
  };
}

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.value()); // 2
Pułapka

Klasyczna pułapka: pętla for z var i setTimeout wewnątrz loguje ostatnią wartość licznika dla każdej iteracji. Powód: var ma zasięg funkcji, więc wszystkie callbacki współdzielą tę samą zmienną.

Pułapka var w pętli asynchronicznej javascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// wypisuje: 3, 3, 3
Poprawka: let tworzy nowe wiązanie na iterację javascript
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// wypisuje: 0, 1, 2
  • Enkapsulacja prywatnego stanu (module revealing pattern) bez klas.
  • Memoizacja: cache wyników funkcji trzymany w domknięciu między wywołaniami.
  • Fabryki funkcji (curry, partial application) z zamrożonymi argumentami.
  • Debounce/throttle: domknięcie przechowuje id timera i timestamp między wywołaniami.
Memoizacja przez domknięcie javascript
function memoize(fn) {
  const cache = new Map();
  return (arg) => {
    if (cache.has(arg)) return cache.get(arg);
    const result = fn(arg);
    cache.set(arg, result);
    return result;
  };
}

Pętla z var (błąd)

  • Jedna wspólna zmienna i dla wszystkich iteracji
  • setTimeout loguje wartość po zakończeniu pętli
  • Typowy wynik: 3, 3, 3

Pętla z let (poprawnie)

  • Nowe wiązanie i tworzone przy każdej iteracji
  • Każde domknięcie łapie własną kopię i
  • Wynik: 0, 1, 2
Koszt pamięciowy

Domknięcie utrzymuje przy życiu cały leksykalny scope, w którym powstało, nie tylko zmienne faktycznie używane. Silniki V8 częściowo to optymalizują, ale nie należy na tym polegać.

Fakt na test

Długo żyjące domknięcia trzymające referencje do dużych obiektów lub węzłów DOM są jedną z głównych przyczyn wycieków pamięci w aplikacjach SPA.

Mnemonik

Domknięcie = plecak: funkcja nosi ze sobą zmienne z miejsca urodzenia, gdziekolwiek zostanie wywołana.

Dalej: 04 `this`, prototypy i klasy pod maską

`this`, prototypy i klasy pod maską

na egzaminie

Wartość this nie jest ustalana w miejscu zdefiniowania funkcji (poza arrow functions), lecz w miejscu jej wywołania (call site). To najczęstsze źródło bugów przy przekazywaniu metod jako callbacki.

Forma wywołaniaWartość this
obj.method()obj (odbiornik wywołania)
function() w strict mode (samodzielnie)undefined
function() w sloppy mode (samodzielnie)globalThis (np. window)
Arrow functionthis z otaczającego zakresu leksykalnego (nie zmienia się nigdy)
new Ctor()nowo utworzony obiekt
fn.call(ctx) / fn.apply(ctx) / fn.bind(ctx)jawnie podany ctx
Metoda w klasieinstancja, na której wywołano metodę (strict mode zawsze)
Arrow function dziedziczy this leksykalnie javascript
const obj = {
  value: 42,
  regular: function () { return this.value; },
  arrow: () => this?.value,
};
console.log(obj.regular()); // 42
console.log(obj.arrow());   // undefined - this to zakres modułu, nie obj
Pułapka

Przekazanie metody jako referencji (np. setTimeout(obj.method, 1000) albo button.addEventListener('click', obj.method)) odrywa metodę od obiektu. Wewnątrz method this przestaje wskazywać na obj, bo o wartości this decyduje sposób wywołania, nie miejsce definicji.

Naprawa przez arrow function jako pole klasy javascript
class Button {
  label = 'OK';
  handleClick = () => console.log(this.label); // this związane raz, przy tworzeniu instancji
}

const b = new Button();
const el = document.querySelector('button');
el.addEventListener('click', b.handleClick); // działa poprawnie
Termin kluczowy

Łańcuch prototypów (prototype chain): Mechanizm dziedziczenia w JS: każdy obiekt ma wewnętrzną referencję [[Prototype]] (dostępną przez Object.getPrototypeOf) do innego obiektu. Odczyt właściwości, której nie ma na obiekcie, przechodzi w górę łańcucha aż do Object.prototype lub null.

class jako cukier składniowy nad prototypami javascript
class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} robi dźwięk`; }
}

// odpowiednik pod maską:
function AnimalFn(name) { this.name = name; }
AnimalFn.prototype.speak = function () {
  return `${this.name} robi dźwięk`;
};
Różnice class vs function constructor

Metody zdefiniowane w class są nieenumerowalne (nie pojawiają się w for...in ani Object.keys na prototypie), w przeciwieństwie do metod dopisanych ręcznie do prototype.

Fakt na test

Deklaracje class działają zawsze w strict mode i muszą być wywoływane z new (wywołanie bez new rzuca TypeError). Funkcje konstruktorów tego nie wymuszają.

  • extends ustawia [[Prototype]] klasy potomnej na klasę bazową przez Object.setPrototypeOf.
  • super() musi zostać wywołane przed użyciem this w konstruktorze klasy potomnej.
  • Klasy są hoistowane do TDZ (jak let/const), function constructors są hoistowane w pełni.
  • Pola instancji (class fields) są przypisywane na początku konstruktora, przed jego resztą ciała.
Mnemonik

this = kto zadzwonił, nie kto napisał numer. Arrow function nie odbiera telefonu, tylko podaje dalej ten sam numer co rodzic.

Dalej: 05 Event loop: microtaski vs makrotaski

Event loop: microtaski vs makrotaski

na egzaminie

JavaScript jest single-threaded, ale event loop pozwala obsługiwać operacje asynchroniczne bez blokowania wątku głównego. Kluczowe jest rozróżnienie microtask queue i macrotask (task) queue oraz zasada: po każdym pojedynczym makrotasku silnik opróżnia CAŁĄ kolejkę microtasków, zanim przejdzie dalej.

Termin kluczowy

Call stack: Struktura LIFO, w której silnik śledzi aktualnie wykonywane wywołania funkcji. Kod synchroniczny wykonuje się w całości, zanim event loop przejdzie do kolejek zadań.

Termin kluczowy

Microtask queue: Kolejka o wyższym priorytecie zasilana przez: callbacki .then/.catch/.finally Promise, queueMicrotask() oraz wznowienia po await. Cała kolejka microtasków jest opróżniana (łącznie z microtaskami dodanymi w trakcie jej opróżniania) przed przejściem do kolejnego makrotasku lub renderowania.

Termin kluczowy

Macrotask (task) queue: Kolejka zasilana przez setTimeout, setInterval, I/O, eventy UI i (w przeglądarce) requestAnimationFrame w osobnej kategorii. Po każdym pojedynczym makrotasku silnik wraca do opróżnienia microtask queue, dopiero potem bierze kolejny makrotask.

Dokładna kolejność wykonania javascript
console.log('start');

setTimeout(() => console.log('timeout'), 0);

Promise.resolve().then(() => console.log('promise 1'));

queueMicrotask(() => console.log('queueMicrotask'));

Promise.resolve().then(() => console.log('promise 2'));

console.log('end');

// kolejność: start, end, promise 1, queueMicrotask, promise 2, timeout
  1. 1. Kod synchroniczny 'start' i 'end' logują się od razu, call stack musi się opróżnić zanim ruszy event loop.
  2. 2. Opróżnienie microtask queue 'promise 1', 'queueMicrotask', 'promise 2' wykonują się w kolejności FIFO dodania do kolejki, całość przed jakimkolwiek makrotaskiem.
  3. 3. Pierwszy makrotask Dopiero teraz silnik bierze callback z setTimeout i loguje 'timeout', mimo delay 0.
ŹródłoTyp kolejki
Promise.then/catch/finallymicrotask
queueMicrotask()microtask
await (wznowienie po nim)microtask
setTimeout / setIntervalmacrotask
setImmediate (Node.js)macrotask (po fazie I/O)
Zdarzenia DOM (click, input)macrotask
MutationObservermicrotask
Pułapka

setTimeout(fn, 0) nie oznacza 'wykonaj natychmiast' - oznacza 'wykonaj jak najszybciej po opróżnieniu obecnego call stacka i wszystkich microtasków'. Kod z Promise zawsze wykona się przed setTimeout(fn, 0) zaplanowanym wcześniej w tej samej turze.

process.nextTick w Node.js

W Node.js process.nextTick() ma wyższy priorytet niż microtaski Promise: cała kolejka nextTick jest opróżniana przed przejściem do microtask queue. To zachowanie specyficzne dla Node, nieobecne w specyfikacji przeglądarkowej ECMA-262/HTML.

Mnemonik

Microtask = VIP przy wejściu, wchodzi przed każdym kolejnym makrotaskiem. Makrotask = zwykła kolejka, jeden na turę.

async/await to cukier nad microtaskami javascript
async function example() {
  console.log('A');
  await null; // wymusza wznowienie jako microtask
  console.log('B');
}
example();
console.log('C');
// kolejność: A, C, B
Dalej: 06 async/await i kombinatory Promise

async/await i kombinatory Promise

na egzaminie

async/await to składniowy cukier nad Promise, ułatwiający czytanie kodu asynchronicznego jako sekwencji kroków, przy zachowaniu w pełni asynchronicznej, nieblokującej semantyki pod spodem.

Podstawowy wzorzec try/catch javascript
async function fetchUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error('Nie udało się pobrać użytkownika', err);
    throw err; // propaguj dalej, nie połykaj cicho
  }
}
Pułapka

Sekwencyjne await na niezależnych operacjach niepotrzebnie serializuje czas wykonania. Kod await fetchA(); await fetchB(); czeka na zakończenie A, zanim W OGÓLE zacznie B. Obie operacje mogłyby biec równolegle.

Naprawa: równoległość przez Promise.all javascript
// źle: sumaryczny czas obu operacji, sekwencyjnie
const a = await fetchA();
const b = await fetchB();

// dobrze: obie operacje startują równolegle
const [a2, b2] = await Promise.all([fetchA(), fetchB()]);
MetodaKiedy się rozstrzygaWynik przy sukcesieWynik przy porażce
Promise.allgdy wszystkie fulfilled, lub od razu przy pierwszym rejectedtablica wartości w oryginalnej kolejnościodrzucenie z powodem pierwszego rejected
Promise.allSettledgdy wszystkie się rozstrzygną (fulfilled lub rejected)tablica {status, value} lub {status, reason}nigdy się nie odrzuca
Promise.racegdy pierwsza się rozstrzygnie (fulfilled lub rejected)wartość pierwszej rozstrzygniętejpowód pierwszej odrzuconej, jeśli to ona wygra wyścig
Promise.anygdy pierwsza zostanie fulfilledwartość pierwszej fulfilledAggregateError, gdy wszystkie rejected
Promise.any i AggregateError javascript
try {
  const first = await Promise.any([
    fetch('/mirror-a'),
    fetch('/mirror-b'),
  ]);
} catch (err) {
  console.error(err instanceof AggregateError); // true, gdy oba mirrory padły
  console.error(err.errors); // tablica wszystkich powodów odrzucenia
}
Promise.allSettled - żaden wynik nie ginie javascript
const results = await Promise.allSettled([fetchA(), fetchB()]);
// [{status:'fulfilled', value: ...}, {status:'rejected', reason: ...}]
const ok = results.filter(r => r.status === 'fulfilled').map(r => r.value);
  • try/catch wokół await łapie zarówno błędy synchroniczne, jak i odrzucenia promisów.
  • Niezłapane odrzucenie promisa emituje event unhandledrejection (przeglądarka) lub 'unhandledRejection' (Node) - zawsze dodawaj listener w aplikacjach produkcyjnych.
  • Promise.all fail-fast: jedno odrzucenie przerywa oczekiwanie na resztę, nawet jeśli reszta i tak wykona się w tle.
  • Do równoległych operacji, gdzie porażka części jest akceptowalna, używaj allSettled, nie all.
async function zawsze zwraca Promise

Nawet return 42 wewnątrz funkcji async faktycznie zwraca Promise.resolve(42) do wywołującego. Rzucenie wyjątku (throw) wewnątrz async function jest równoważne zwróceniu odrzuconego promisa, nie przerywa programu synchronicznie.

Mnemonik

all = wszystko albo nic (fail-fast). allSettled = raport ze wszystkiego. race = kto pierwszy dobiegnie (dobry lub zły). any = pierwszy dobry wygrywa.

Dalej: 07 Nowoczesne API: optional chaining, nullish coalescing, nowe metody Array/Object

Nowoczesne API: optional chaining, nullish coalescing, nowe metody Array/Object

ES2020-ES2024 wprowadziły grupę drobnych, ale codziennie używanych usprawnień. Są to: bezpieczny dostęp do zagnieżdżonych właściwości, precyzyjniejsza obsługa wartości domyślnych, niemutujące warianty klasycznych metod tablicowych oraz naturalne grupowanie danych bez bibliotek typu lodash.

Optional chaining + nullish coalescing javascript
const city = user?.address?.city ?? 'nieznane';
const total = config?.limits?.max ?? 100;
user?.notify?.(); // wywołanie opcjonalnej metody, bez rzucania jeśli notify nie istnieje
Pułapka

?? różni się od || tym, że zatrzymuje się TYLKO na null lub undefined, nie na innych falsy wartościach. Wyrażenie count || 10 zwróci 10 nawet gdy count wynosi 0, co jest zwykle błędem. Wyrażenie count ?? 10 poprawnie zwróci 0.

?? vs || na wartości 0 javascript
const count = 0;
console.log(count || 10); // 10 - błąd logiczny, 0 jest falsy
console.log(count ?? 10); // 0 - poprawne, 0 nie jest null/undefined
Array.prototype.at - indeksowanie od końca javascript
const arr = [10, 20, 30];
console.log(arr.at(-1));          // 30
console.log(arr[arr.length - 1]); // 30, ten sam wynik, mniej czytelnie
Niemutujące metody tablicowe (ES2023) javascript
const nums = [3, 1, 2];
const sorted = nums.toSorted();      // [1, 2, 3], nums bez zmian
const reversed = nums.toReversed();  // [2, 1, 3], nums bez zmian
const replaced = nums.with(0, 99);   // [99, 1, 2], nums bez zmian
console.log(nums); // [3, 1, 2] - oryginał nietknięty
Object.groupBy (ES2024) javascript
const inventory = [
  { name: 'apple', type: 'fruit' },
  { name: 'carrot', type: 'vegetable' },
  { name: 'banana', type: 'fruit' },
];
const byType = Object.groupBy(inventory, item => item.type);
// { fruit: [apple, banana], vegetable: [carrot] } - obiekt bez prototypu
structuredClone dla głębokiego kopiowania javascript
const original = { date: new Date(), items: new Map([['a', 1]]) };
const copy = structuredClone(original);
copy.items.set('b', 2);
console.log(original.items.has('b')); // false - niezależna kopia
Mutująca (oryginał)Niemutująca (ES2023)
sort()toSorted()
reverse()toReversed()
splice()toSpliced()
arr[i] = xwith(i, x)
Czego structuredClone nie sklonuje

Funkcje i węzły DOM rzucają DataCloneError. Prototyp obiektu nie jest zachowywany - instancja klasy po sklonowaniu przestaje być instanceof tej klasy. Gettery/settery i property descriptory są tracone, w kopii stają się zwykłymi właściwościami read/write.

Mnemonik

?? = 'tylko null/undefined mnie zatrzyma'. || = 'każdy falsy mnie zatrzyma'. to* (toSorted, toReversed, toSpliced) = 'zwracam nowość, oryginał zostaje'.

Dalej: 08 Moduły ESM vs CommonJS oraz wycieki pamięci

Moduły ESM vs CommonJS oraz wycieki pamięci

na egzaminie

ECMAScript Modules (ESM, import/export) i CommonJS (require/module.exports) różnią się nie tylko składnią, ale fundamentalną semantyką ładowania. ESM analizuje kod statycznie i asynchronicznie, CommonJS wykonuje go dynamicznie i synchronicznie. Interop między nimi to częste źródło błędów w Node.js.

ESM (import/export)

  • Statyczna struktura, analizowana przed wykonaniem (umożliwia tree-shaking)
  • Bindingi żywe: zaimportowana wartość aktualizuje się przy zmianie w module źródłowym
  • Domyślnie strict mode, wspiera top-level await
  • Ładowanie może być asynchroniczne (fetch modułów w przeglądarce)

CommonJS (require/module.exports)

  • Dynamiczne wywołanie require() w dowolnym miejscu kodu, w tym warunkowo
  • Eksport to kopia wartości w momencie require (dla prymitywów)
  • Synchroniczne ładowanie, blokujące I/O
  • Brak top-level await, brak natywnego tree-shakingu
Składnia ESM javascript
// utils.mjs
export const PI = 3.14159;
export function double(x) { return x * 2; }
export default class Calculator {}

// main.mjs
import Calculator, { PI, double } from './utils.mjs';
Składnia CommonJS javascript
// utils.cjs
const PI = 3.14159;
function double(x) { return x * 2; }
module.exports = { PI, double };

// main.cjs
const { PI, double } = require('./utils.cjs');
Interop CJS ⇄ ESM w Node.js

W starszych Node.js (<22.12) require() nie mógł ładować ESM - trzeba było użyć dynamicznego import(). Od Node 22.12+/23 require() domyślnie potrafi synchronicznie załadować moduł ESM, o ile ten nie używa top-level await.

Fakt na test

Dla modułów z top-level await nadal wymagany jest dynamiczny import(). Import CommonJS z modułu ESM działa: module.exports trafia jako default export. Node dodatkowo próbuje wystawić named exports przez statyczną analizę (cjs-module-lexer), co nie zawsze wykrywa dynamiczne przypisania.

  • Zapomniane event listenery: addEventListener bez odpowiadającego removeEventListener trzyma referencję do całego zamknięcia i elementu.
  • Odłączone (detached) węzły DOM: element usunięty z drzewa DOM, ale wciąż referencjonowany przez zmienną JS lub domknięcie, nie może zostać zwolniony przez garbage collector.
  • Timery bez czyszczenia: setInterval/setTimeout przechowujące w domknięciu referencje do dużych obiektów, nigdy niewyczyszczone przez clearInterval/clearTimeout.
  • Rosnące globalne cache/Map bez limitu rozmiaru lub strategii eksmisji (np. LRU), akumulujące dane przez cały czas życia aplikacji.
Detached DOM node - wyciek i naprawa javascript
let cachedRows = [];

function renderTable(rows) {
  document.querySelector('#table').replaceChildren(...rows);
  cachedRows.push(...rows); // wyciek: każde wywołanie dokłada wiersze, stare zostają w tablicy po usunięciu z DOM
}

// naprawa: nie trzymaj referencji dłużej niż potrzeba
function renderTableFixed(rows) {
  document.querySelector('#table').replaceChildren(...rows);
  // brak długożyjącej referencji poza zakresem funkcji
}
Pułapka

Weźmy listener na długo żyjącym obiekcie (np. window, document, EventEmitter singleton), z callbackiem-domknięciem, które przechwytuje duży obiekt lokalny. Taki listener utrzymuje ten obiekt przy życiu tak długo, jak sam istnieje, nawet jeśli komponent, który go dodał, dawno zniknął z UI.

  1. 1. Zrób heap snapshot Chrome DevTools → Memory → Heap snapshot, przed i po scenariuszu podejrzanym o wyciek.
  2. 2. Wymuś GC i porównaj Kliknij 'Collect garbage', zrób drugi snapshot, użyj widoku 'Comparison' do zobaczenia obiektów, które przybyły i nie zostały zwolnione.
  3. 3. Szukaj Detached Filtruj po 'Detached' w widoku Containment - to węzły DOM usunięte z drzewa, ale nadal w pamięci.
  4. 4. Prześledź retainers Panel 'Retainers' pokazuje dokładny łańcuch referencji (closure → zmienna → obiekt) trzymający obiekt przy życiu - tam jest źródło wycieku.
Mnemonik

Wyciek pamięci w JS to zawsze 'ktoś wciąż trzyma referencję'. Znajdź trzymającego (retainer), nie ofiarę.

Sprawdź się

20 z 24 pytań, losowo, z wyjaśnieniem po każdym. Wynik zostaje na tym urządzeniu.

Rozpocznij quiz

Fiszki: JavaScript zaawansowany

Pytanie

O tym demo

Działające demo kursów napisanych przez Tenzan Logic. Wyniki zostają na tym urządzeniu, bez konta.

Każdy kurs, pytanie i fiszkę napisał Tenzan Logic, po polsku i od zera. Jeśli chcesz taki zestaw dla swojego zespołu, z Twoimi procedurami i Twoim słownictwem, napisz.

Napisz do Tenzan Logic

Jak to działa

100% czas (dni) bez powtórek 1 3 7 21 z powtórkami
Schemat idei, nie dane pomiarowe (Ebbinghaus 1885; Cepeda i in. 2006).
  • Odstępy

    1·3·7·21 dni do powtórki

    Fiszka z „Umiem” wraca po 1, 3, 7 i 21 dniach.

    Badanie

    Powtórki z przerwami dają trwalszą pamięć niż jedna po drugiej. Cepeda i in. 2006, Psychological Bulletin 132(3): 354-380

  • Przypominanie

    ukryta odpowiedź fiszki

    Najpierw odpowiedz z pamięci, potem odwróć.

    Badanie

    Próba przypomnienia dała lepszą pamięć po dwóch dniach i po tygodniu niż ponowne czytanie. Roediger i Karpicke 2006, Psychological Science 17(3): 249-255

  • Dwa kody

    4 kolory bloków

    Fakt, termin, mnemonik i pułapka mają swój kolor.

    Badanie

    Teoria: słowo i obraz zapisują się osobno. To teoria, nie gwarancja wyniku. Paivio 1991, Canadian Journal of Psychology 45(3): 255-287

  • Porcje

    167 krótkich sekcji

    Jedna sekcja to jedna porcja do zapamiętania.

    Badanie

    Pamięć robocza mieści około czterech porcji (Miller 1956: siedem, Cowan 2001: cztery). Cowan 2001, Behavioral and Brain Sciences 24(1): 87-114

  • Skróty

    134 zielone bloki

    Mnemonik zbiera kilka elementów w jeden, np. „SUR”.

    Badanie

    Skrót zbiera kilka elementów w jeden; przykład „SUR” jest w kursie o podstawach AI. Opis funkcji aplikacji, bez cytowanego badania

  • Testy

    20 z 24 pytań w quizie

    Po każdej odpowiedzi widzisz wyjaśnienie.

    Badanie

    Ćwiczenie, nie sprawdzian: pytania są w losowej kolejności i uzupełniają fiszki, nie zastępują ich. Efekt testowania: zob. „Przypominanie”

Szukaj

Postęp

Brak wyników. Rozwiąż pierwszy quiz.

Tu zbiera się Twój postęp: rekord i medal każdego quizu, seria dni i pytania do powtórki.

Gdzie jest zapis
Tylko w pamięci tej przeglądarki. Konta nie ma i wyniki nigdzie nie są wysyłane. Inna przeglądarka lub inne urządzenie zaczyna od zera.
Jak wyczyścić
W quizie: Historia, potem Wyczyść historię. Fiszki i serię dni usuniesz razem z danymi witryny w ustawieniach przeglądarki.
Wybierz quiz

Progi medali

Medal liczy się z najlepszego wyniku quizu: Brąz od 60%, Srebro od 75%, Złoto od 90%, Platyna przy 100%.