Przejdź do treści

Temat 23 · 2 × 45 min

Zasięg, hoisting, domknięcia i this

Wiesz już, że zmienna zadeklarowana w funkcji nie jest widoczna poza nią. Ta lekcja porządkuje dlaczego – i co z tego wynika, gdy funkcje tworzą inne funkcje albo są wywoływane później (w setTimeout, w addEventListener). Trzy pojęcia – zasięg (scope), domknięcie (closure) i this – stoją za większością „dziwnych” zachowań JavaScriptu, na które natkniesz się w cudzym kodzie, w pytaniach rekrutacyjnych i w React. Dobra wiadomość: reguł jest kilka i są spójne.

Zasięg: blokowy i funkcyjny

Zasięg (scope) to obszar kodu, w którym nazwa zmiennej jest widoczna. W JavaScripcie są trzy poziomy:

  • globalny – zmienne zadeklarowane poza wszystkimi funkcjami i blokami; widoczne wszędzie,
  • funkcyjny – zmienne zadeklarowane w funkcji; widoczne tylko w niej (i w funkcjach zagnieżdżonych),
  • blokowy – zmienne z let/const zadeklarowane w { … } (pętla, if, zwykły blok); widoczne tylko w tym bloku.

Różnica między let/const a var sprowadza się do tego trzeciego poziomu: var ignoruje bloki i widzi tylko funkcje. Dlatego zmienna z var zadeklarowana w if czy w pętli „wycieka” poza nie.

Sprawdź się

Co wypisze ten kod?

if (true) {
var a = 1;
let b = 2;
}
console.log(a);
console.log(b);
zasieg.js
const globalna = 'widoczna wszędzie';

function pokaz() {
  const lokalna = 'tylko w funkcji';
  if (true) {
    var funkcyjna = 'var widzi funkcję, nie blok';
    let blokowa = 'tylko w if';
    console.log(globalna, '|', lokalna, '|', blokowa);
  }
  console.log(funkcyjna);   // działa – var „wyciekło” z bloku if
  // console.log(blokowa);  // ReferenceError – let zostało w bloku
}
pokaz();

console.log(typeof funkcyjna); // 'undefined' – funkcyjna była wewnątrz pokaz()
console.log(lokalna);          // ReferenceError: lokalna is not defined
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik

Zasięgi się zagnieżdżają: funkcja wewnętrzna widzi zmienne zewnętrznej, ale nie odwrotnie. Gdy silnik szuka nazwy, idzie od najbardziej wewnętrznego zasięgu na zewnątrz i bierze pierwszą, którą znajdzie. Jeśli w środku zadeklarujesz zmienną o tej samej nazwie co na zewnątrz, zewnętrzna jest w tym bloku przesłonięta (shadowing) – nie zmieniona, tylko niewidoczna.

przeslanianie.js
let poziom = 'zewnętrzny';

function test() {
  let poziom = 'wewnętrzny'; // nowa zmienna, przesłania zewnętrzną
  console.log('w funkcji:', poziom);
}
test();
console.log('na zewnątrz:', poziom); // zewnętrzna nietknięta

// bez let/const w środku – to przypisanie do zewnętrznej, nie nowa zmienna
function zmien() {
  poziom = 'zmieniony';
}
zmien();
console.log('po zmien():', poziom);
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik

Hoisting i strefa martwa (TDZ)

Przed wykonaniem kodu silnik najpierw „skanuje” zasięg i rejestruje wszystkie deklaracje. To zjawisko nazywa się hoisting (wyniesienie). Jego skutki różnią się w zależności od sposobu deklaracji:

DeklaracjaCo jest wynoszoneOdczyt przed linią deklaracji
function f() {}cała funkcja z ciałemdziała – można wywołać
var x = 1tylko nazwa (wartość undefined)undefined, bez błędu
let x = 1, const x = 1nazwa, ale bez dostępuReferenceError (TDZ)
const f = () => {}jak constReferenceError

Obszar od początku bloku do linii z let/const to strefa martwa (Temporal Dead Zone, TDZ): zmienna „już istnieje”, ale jeszcze nie wolno jej dotknąć. To celowe – błąd jest lepszy niż ciche undefined.

Sprawdź się

Co wypisze ten kod?

console.log(a);
var a = 10;
console.log(typeof b);
function b() {}
hoisting.js
// deklaracja funkcji – można wywołać przed napisaniem
console.log(powitanie('Ola'));
function powitanie(imie) {
  return `Cześć, ${imie}!`;
}

// var – nazwa wyniesiona, wartość jeszcze nie
console.log(licznik);  // undefined (nie błąd!)
var licznik = 5;
console.log(licznik);  // 5
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik
tdz.js
// let/const – strefa martwa
function pokaz() {
  console.log('przed deklaracją…');
  console.log(cena);   // ReferenceError: Cannot access 'cena' before initialization
  let cena = 100;
}
pokaz();
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik

To dlatego funkcje strzałkowe przypisane do const trzeba zdefiniować przed użyciem, a zwykłe function można trzymać na dole pliku. Oba style są poprawne; w zespole trzymaj się jednego.

Klasyka: var w pętli i setTimeout

Najsłynniejszy skutek zasięgu funkcyjnego var. Pętla rejestruje trzy funkcje do wykonania „za chwilę”. Gdy w końcu się wykonują, pętla dawno się skończyła – i wszystkie trzy patrzą na tę samą zmienną i, która ma wartość końcową.

Sprawdź się

Co wypisze ten kod (po chwili)?

for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
var-vs-let-petla.js
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log('var:', i), 0);
}

for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log('let:', j), 0);
}
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik

Z let każda iteracja pętli dostaje własną, świeżą zmienną j, a callback zapamiętuje tę, która istniała w jego iteracji. To właśnie domknięcie – i powód, dla którego ta lekcja łączy zasięg z closure. Przed ES6 obchodzono to funkcją natychmiast wywoływaną (IIFE), którą jeszcze zobaczysz w starszym kodzie: (function (k) { setTimeout(function () { console.log(k); }); })(i);.

Domknięcie (closure)

Domknięcie to funkcja razem ze zmiennymi z zasięgu, w którym powstała. Gdy funkcja wewnętrzna odwołuje się do zmiennej funkcji zewnętrznej, ta zmienna nie znika po zakończeniu zewnętrznej – żyje tak długo, jak długo żyje wewnętrzna funkcja. Funkcja „pamięta” swoje otoczenie.

Najprostszy przykład: fabryka liczników. Funkcja zrobLicznik tworzy zmienną stan i zwraca funkcję, która ją zwiększa. Z zewnątrz do stan nie ma dostępu – można go zmieniać tylko przez zwróconą funkcję.

Sprawdź się

Co wypisze ten kod?

function zrobLicznik() {
let stan = 0;
return () => ++stan;
}
const a = zrobLicznik();
const b = zrobLicznik();
a(); a();
console.log(a(), b());
licznik.js
function zrobLicznik(start = 0) {
  let stan = start;           // „prywatna” zmienna – widzi ją tylko zwrócona funkcja

  return () => {
    stan++;
    return stan;
  };
}

const kliknieciaA = zrobLicznik();
const kliknieciaB = zrobLicznik(100);

console.log(kliknieciaA(), kliknieciaA(), kliknieciaA()); // 1 2 3
console.log(kliknieciaB());                               // 101 – własny stan
console.log(typeof stan);                                 // 'undefined' – z zewnątrz niedostępne
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik

Zwróć uwagę: po return funkcja zrobLicznik już się skończyła, a mimo to stan istnieje i jest aktualizowany. Każde wywołanie fabryki daje nową, niezależną parę (zmienna + funkcja).

Obiekt z prywatnym stanem

Fabryka może zwrócić kilka funkcji w obiekcie – wszystkie dzielą ten sam stan, a świat zewnętrzny widzi tylko to, co zwróciłeś. To wzorzec, który przed klasami (i polami prywatnymi #, zob. lekcję o klasach) służył do hermetyzacji.

konto.js
function zrobKonto(saldoPoczatkowe) {
  let saldo = saldoPoczatkowe;
  const historia = [];

  return {
    wplac(kwota) {
      saldo += kwota;
      historia.push(`+${kwota}`);
    },
    wyplac(kwota) {
      if (kwota > saldo) return false;
      saldo -= kwota;
      historia.push(`-${kwota}`);
      return true;
    },
    stan: () => saldo,
    historia: () => [...historia], // kopia, żeby nikt nie zmienił oryginału
  };
}

const konto = zrobKonto(100);
konto.wplac(50);
console.log(konto.wyplac(500)); // false – za mało
console.log(konto.wyplac(30));  // true
console.log(konto.stan(), konto.historia());
console.log(konto.saldo);       // undefined – saldo nie jest właściwością, tylko zmienną w domknięciu
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik

Domknięcie w obsłudze zdarzeń – licznik bez zmiennej globalnej

W lekcji o stanie aplikacji licznik trzymałeś w zmiennej globalnej. Domknięcie pozwala zamknąć stan w funkcji, która konfiguruje przycisk – a wtedy możesz mieć wiele niezależnych liczników z tego samego kodu.

Trzy niezależne liczniki
function podlaczLicznik(przycisk) {
  let liczba = 0; // osobna zmienna dla każdego przycisku
  przycisk.addEventListener('click', () => {
    liczba++;
    przycisk.textContent = `Kliknięto: ${liczba}`;
  });
}

document.querySelectorAll('.licznik').forEach(podlaczLicznik);
Podgląd

debounce – opóźnij reakcję, aż użytkownik skończy pisać

Praktyczny i bardzo częsty w pracy przykład domknięcia. Pole wyszukiwania wysyła zapytanie przy każdym wciśniętym klawiszu – to marnotrawstwo. debounce(fn, ms) zwraca nową funkcję, która wywoła fn dopiero wtedy, gdy przez ms milisekund nie było kolejnego wywołania. Identyfikator timera żyje w domknięciu między wywołaniami.

debounce.js
function debounce(fn, ms) {
  let timer;                     // pamiętany między wywołaniami
  return (...args) => {
    clearTimeout(timer);         // odwołaj poprzednie oczekiwanie
    timer = setTimeout(() => fn(...args), ms);
  };
}

const szukaj = debounce((fraza) => console.log('Szukam:', fraza), 50);

// symulacja szybkiego pisania – tylko ostatnie wywołanie dotrze do fn
szukaj('j');
szukaj('ja');
szukaj('jav');
szukaj('java');
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik
Pole wyszukiwania z debounce
function debounce(fn, ms) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), ms);
  };
}

const pole = document.getElementById('szukaj');
let zwykle = 0;
let opoznione = 0;

const wyszukaj = debounce((fraza) => {
  opoznione++;
  document.getElementById('opoznione').textContent = opoznione;
  document.getElementById('fraza').textContent = fraza || '–';
}, 400);

pole.addEventListener('input', () => {
  zwykle++;
  document.getElementById('zwykle').textContent = zwykle;
  wyszukaj(pole.value);
});
Podgląd

this – kto wywołał funkcję

this to nie zmienna, którą deklarujesz – to słowo kluczowe, którego wartość zależy od tego, jak funkcja została wywołana, a nie gdzie została napisana. Cztery reguły, które pokrywają prawie wszystko:

  1. obiekt.metoda() – this to obiekt (ten przed kropką).
  2. funkcja() bez kropki – this to obiekt globalny (window w przeglądarce), a w trybie ścisłym (moduły, klasy) undefined.
  3. Funkcja strzałkowa – nie ma własnego this; bierze je z miejsca, w którym została napisana (z otaczającej funkcji).
  4. Obsługa zdarzenia el.addEventListener('click', function () {…}) – this to element, na którym nasłuchujesz (ale w strzałce – zob. regułę 3 – już nie).
Sprawdź się

Co wypisze ten kod?

const uczen = {
imie: 'Kasia',
przedstaw() { return 'Jestem ' + this.imie; },
};
const f = uczen.przedstaw;
console.log(uczen.przedstaw());
console.log(f());
this-metoda.js
const koszyk = {
  pozycje: ['mysz', 'kabel'],
  pokaz() {
    return `W koszyku: ${this.pozycje.join(', ')}`;
  },
  czyToJa() {
    return this === koszyk;
  },
};

console.log(koszyk.pokaz());          // reguła 1: this = koszyk

const oderwana = koszyk.pokaz;        // sama funkcja, bez koszyka
try {
  console.log(oderwana());            // reguła 2: this = obiekt globalny → this.pozycje to undefined
} catch (e) {
  console.log('Błąd:', e.message);    // Cannot read properties of undefined (reading 'join')
}

// ten sam problem przy przekazaniu metody jako callbacku
console.log(koszyk.czyToJa());        // true
setTimeout(() => console.log('w setTimeout(koszyk.czyToJa):', koszyk.czyToJa()), 0); // true – wywołanie z kropką
const callback = koszyk.czyToJa;
setTimeout(() => console.log('oderwany callback:', callback()), 0);                  // false – „bez kropki”
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik

Dokładnie to dzieje się przy setTimeout(obiekt.metoda, 1000) czy addEventListener('click', obiekt.metoda): przeglądarka wywołuje funkcję sama, bez obiektu przed kropką, więc this w środku nie jest obiektem.

Strzałki dziedziczą this – stąd ich popularność w callbackach

this-strzalka.js
const licznikSekund = {
  sekundy: 0,
  startZwykly() {
    setTimeout(function () {
      // this to NIE licznikSekund – zwykła funkcja wywołana przez setTimeout
      console.log('zwykła funkcja:', this === licznikSekund, typeof this.sekundy);
    }, 0);
  },
  startStrzalka() {
    setTimeout(() => {
      // strzałka bierze this z metody startStrzalka, czyli licznikSekund
      this.sekundy++;
      console.log('strzałka:', this === licznikSekund, this.sekundy);
    }, 0);
  },
};

licznikSekund.startZwykly();
licznikSekund.startStrzalka();
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik

To działa też w forEach, map i każdym innym callbacku wewnątrz metody. Reguła praktyczna: metody obiektów i klas pisz zwykłą składnią nazwa() {} (potrzebują własnego this), callbacki wewnątrz nich pisz strzałkami (mają dziedziczyć this). Odwrotnie będzie źle: metoda-strzałka w literale obiektu nie widzi obiektu, bo bierze this z zewnątrz, gdzie obiektu jeszcze nie ma.

Naprawianie this: bind

Gdy musisz przekazać metodę jako callback (np. do addEventListener) i zachować jej this, użyj metoda.bind(obiekt) – zwraca nową funkcję z this przypiętym na stałe. Alternatywa to opakowanie w strzałkę: () => obiekt.metoda().

bind.js
const gracz = {
  imie: 'Nova',
  punkty: 0,
  dodajPunkt() {
    this.punkty++;
    console.log(`${this.imie}: ${this.punkty}`);
  },
};

const przypieta = gracz.dodajPunkt.bind(gracz);
przypieta();                      // this = gracz, mimo wywołania „bez kropki”
setTimeout(przypieta, 0);         // też działa

setTimeout(() => gracz.dodajPunkt(), 0); // alternatywa – strzałka wywołuje z kropką
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik

this w obsłudze zdarzeń

this w handlerze: function vs strzałka
const wynik = document.getElementById('wynik');

document.getElementById('zwykly').addEventListener('click', function () {
  // this = element, na którym nasłuchujemy
  wynik.textContent = `this.id = ${this.id}, this.tagName = ${this.tagName}`;
});

document.getElementById('strzalka').addEventListener('click', (event) => {
  // strzałka: this z otoczenia (skrypt → window), element bierzemy z event.currentTarget
  wynik.textContent = `this === window: ${this === window}; element to event.currentTarget: ${event.currentTarget.id}`;
});
Podgląd

W nowym kodzie zamiast polegać na this w handlerze używaj event.currentTarget (element z nasłuchiwaczem) lub event.target (element faktycznie kliknięty) – działa jednakowo w obu rodzajach funkcji. Różnicę między nimi omawia lekcja o zdarzeniach zaawansowanych.

Ściąga

Pojęcie / składniaCo znaczyPrzykład
zasięg blokowylet/const widoczne tylko w { }if (x) { let a = 1; } → a niedostępne po }
zasięg funkcyjnyvar widoczne w całej funkcji, ignoruje blokiif (x) { var a = 1; } → a dostępne po }
przesłanianiewewnętrzna deklaracja tej samej nazwy ukrywa zewnętrznąlet x = 1; { let x = 2; }
hoisting functiondeklaracja wyniesiona z ciałemwywołanie przed definicją działa
hoisting varnazwa wyniesiona, wartość undefinedconsole.log(a); var a = 1; → undefined
TDZlet/const przed deklaracjąconsole.log(a); let a = 1; → ReferenceError
var w pętli + setTimeoutjedna zmienna dla wszystkich iteracjiwypisuje 3 3 3
let w pętlinowa zmienna co iteracjęwypisuje 0 1 2
domknięciefunkcja + zmienne z zasięgu, gdzie powstałaconst inc = zrobLicznik(); inc();
fabryka ze stanemzwróć funkcje, które dzielą prywatną zmiennąreturn { wplac, wyplac, stan }
debounce(fn, ms)wywołaj fn dopiero po ms ciszyinput.addEventListener('input', debounce(szukaj, 300))
obj.m()this === objkoszyk.pokaz()
f() (bez kropki)this globalne lub undefined (strict)oderwana metoda traci obiekt
() => {}brak własnego this, bierze z otoczeniacallback w metodzie
f.bind(obj)nowa funkcja z this na stałebtn.addEventListener('click', gra.ruch.bind(gra))
handler functionthis to element z nasłuchiwaczemthis.classList.toggle('on')
event.currentTargetelement z nasłuchiwaczem, niezależnie od thisdziała też w strzałce

Zadania

Zadanie 1
łatwe

Przewidź, zanim uruchomisz

Dla każdego fragmentu zapisz na kartce, co wypisze konsola i dlaczego. Dopiero potem uruchom rozwiązanie i porównaj.

JavaScript
// A
var x = 1;
function f() { var x = 2; return x; }
console.log(f(), x);

// B
let y = 'zewn';
{ let y = 'wewn'; }
console.log(y);

// C
console.log(typeof g, typeof h);
function g() {}
var h = function () {};

// D
for (var i = 0; i < 2; i++) {}
for (let j = 0; j < 2; j++) {}
console.log(i, typeof j);
Pokaż rozwiązanie
przewidz.js
// A: var x w f() to osobna, lokalna zmienna – globalne x bez zmian
var x = 1;
function f() { var x = 2; return x; }
console.log(f(), x);              // 2 1

// B: let w bloku przesłania, ale nie zmienia zewnętrznego y
let y = 'zewn';
{ let y = 'wewn'; }
console.log(y);                   // zewn

// C: deklaracja funkcji g wyniesiona w całości; h to var – wyniesiona tylko nazwa (undefined)
console.log(typeof g, typeof h);  // function undefined
function g() {}
var h = function () {};

// D: var i wycieka z pętli (zostaje 2), let j zostało w pętli
for (var i = 0; i < 2; i++) {}
for (let j = 0; j < 2; j++) {}
console.log(i, typeof j);         // 2 undefined
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik
Zadanie 2
średnie

Fabryka liczników z limitem

Napisz funkcję zrobLicznik(limit), która zwraca obiekt z metodami: zwieksz() (zwiększa o 1, ale nie powyżej limit; zwraca aktualną wartość), zmniejsz() (nie poniżej 0), reset() i wartosc(). Stan licznika ma być niedostępny z zewnątrz (nie może być właściwością obiektu). Utwórz dwa liczniki i pokaż, że są niezależne.

Pokaż rozwiązanie
licznik-limit.js
function zrobLicznik(limit) {
  let stan = 0; // widoczny tylko dla metod poniżej

  return {
    zwieksz() {
      if (stan < limit) stan++;
      return stan;
    },
    zmniejsz() {
      if (stan > 0) stan--;
      return stan;
    },
    reset() {
      stan = 0;
    },
    wartosc: () => stan,
  };
}

const bilety = zrobLicznik(3);
const osoby = zrobLicznik(10);

bilety.zwieksz(); bilety.zwieksz(); bilety.zwieksz();
console.log(bilety.zwieksz());   // 3 – limit zatrzymał
console.log(bilety.zmniejsz());  // 2
console.log(osoby.wartosc());    // 0 – niezależny
console.log(bilety.stan);        // undefined – stan nie jest właściwością

bilety.reset();
console.log(bilety.wartosc());   // 0
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik
Zadanie 3
średnie

Napraw this

Poniższy obiekt ma dwa błędy związane z this. Metoda podsumuj zwraca NaN, a pokazPozniej po chwili rzuca błąd. Znajdź przyczyny i napraw, nie zmieniając struktury obiektu (ma zostać obiektem z trzema metodami).

JavaScript
const zamowienie = {
  pozycje: [{ cena: 20 }, { cena: 35 }],
  rabat: 5,
  podsumuj() {
    let suma = 0;
    this.pozycje.forEach(function (p) {
      suma += p.cena - this.rabat;
    });
    return suma;
  },
  pokazPozniej() {
    setTimeout(this.wypisz, 0);
  },
  wypisz() {
    console.log('Razem:', this.podsumuj());
  },
};
Pokaż rozwiązanie
napraw-this.js
const zamowienie = {
  pozycje: [{ cena: 20 }, { cena: 35 }],
  rabat: 5,
  podsumuj() {
    let suma = 0;
    // Błąd 1: zwykła function w forEach ma własne this (globalne), więc this.rabat to undefined → NaN.
    // Naprawa: strzałka dziedziczy this z metody podsumuj.
    this.pozycje.forEach((p) => {
      suma += p.cena - this.rabat;
    });
    return suma;
  },
  pokazPozniej() {
    // Błąd 2: setTimeout(this.wypisz) wywołuje funkcję „bez kropki” – this nie jest zamowieniem.
    // Naprawa: strzałka zachowująca kropkę (albo this.wypisz.bind(this)).
    setTimeout(() => this.wypisz(), 0);
  },
  wypisz() {
    console.log('Razem:', this.podsumuj());
  },
};

console.log(zamowienie.podsumuj()); // 45
zamowienie.pokazPozniej();          // po chwili: Razem: 45
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik
Zadanie 4
trudne

Funkcja raz() i zapamiętywanie wyników

Napisz dwie funkcje wyższego rzędu oparte na domknięciach:

  1. raz(fn) – zwraca funkcję, która wywoła fn tylko przy pierwszym użyciu, a każde kolejne wywołanie zwraca zapamiętany wynik pierwszego (bez ponownego uruchamiania fn). Przydatne np. do „inicjalizuj tylko raz”.
  2. zapamietaj(fn) (memoize) – zwraca funkcję, która dla tego samego argumentu nie liczy drugi raz, tylko zwraca wynik z pamięci (użyj Map jako pamięci podręcznej). Pokaż na „drogiej” funkcji, która wypisuje liczę… przy każdym prawdziwym obliczeniu.
Pokaż rozwiązanie
raz-i-memo.js
function raz(fn) {
  let wywolana = false;
  let wynik;
  return (...args) => {
    if (!wywolana) {
      wywolana = true;
      wynik = fn(...args); // pierwszy raz: liczymy i zapamiętujemy
    }
    return wynik;          // potem zawsze to samo
  };
}

const polaczZBaza = raz((adres) => {
  console.log('Łączę z', adres);
  return { polaczono: true };
});
console.log(polaczZBaza('db1'));
console.log(polaczZBaza('db2')); // nie wypisze „Łączę” – zwraca wynik z pierwszego razu

function zapamietaj(fn) {
  const pamiec = new Map(); // argument → wynik, żyje w domknięciu
  return (arg) => {
    if (pamiec.has(arg)) return pamiec.get(arg);
    const wynik = fn(arg);
    pamiec.set(arg, wynik);
    return wynik;
  };
}

const kwadrat = zapamietaj((n) => {
  console.log('liczę…', n);
  return n * n;
});
console.log(kwadrat(12)); // liczę… 12 → 144
console.log(kwadrat(12)); // 144 z pamięci, bez „liczę…”
console.log(kwadrat(5));  // liczę… 5 → 25
Konsola
Kliknij „Uruchom”, aby zobaczyć wynik

Test powtórkowy

Test powtórkowy

Zasięg, hoisting, domknięcia i this: 10 pytań.

0/0poprawnych
Sprawdź się

Co wypisze ten kod?

function f() {
if (true) { var a = 'A'; }
return a;
}
console.log(f());
Sprawdź się

Co wypisze ten kod?

console.log(cena);
let cena = 50;
Sprawdź się

Co wypisze ten kod po chwili?

for (let i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), 0);
}
Sprawdź się

Co wypisze ten kod?

function zrob() {
let n = 10;
return () => n += 5;
}
const f = zrob();
f();
console.log(f());
Sprawdź się

Które zdanie o funkcjach strzałkowych jest prawdziwe?

Sprawdź się

Co wypisze ten kod?

const auto = {
marka: 'Fiat',
pokaz() { return this.marka; },
};
const p = auto.pokaz;
console.log(typeof p(), auto.pokaz());
Sprawdź się

Który zapis przekaże metodę gra.ruch do addEventListener tak, by this w niej wskazywało na gra?

Sprawdź się

Co robi funkcja debounce(fn, 300) zwrócona z fabryki opartej na domknięciu?

Sprawdź się

Co wypisze ten kod?

let a = 1;
function zmien() { a = 2; }
function nowa() { let a = 3; }
zmien();
nowa();
console.log(a);
Sprawdź się

Dlaczego po wywołaniu o.start() wartość o.n się nie zmienia?

const o = {
n: 0,
start() { setTimeout(function () { this.n++; }, 0); },
};