Domů O mně Služby Portfolio Učím Blog Kontakt
← Zpět na učím

03.14 Použití Closures

Výuka

Proč closures?

Už jsi viděl tento pattern několikrát v předchozích lekcích:

function makeCounter() {
  let count = 0;

  return function() {
    count++;
    return count;
  };
}

const counter = makeCounter();
console.log(counter());  // → 1
console.log(counter());  // → 2
console.log(counter());  // → 3

Jak je možné, že vnitřní funkce stále vidí count, i když makeCounter už skončila?

To je closure (uzávěr)! Funkce si "pamatuje" proměnné z vnějšího scope, i když ten scope už skončil.

Proč jsou closures užitečné?

  • Soukromá data - count není dostupný zvenku, jen přes funkci
  • Zachování stavu - hodnota přežívá mezi voláními
  • Factory pattern - vytváření funkcí s "nakonfigurovanými" daty
  • Event handlers a callbacky - pamatují si kontext

Představ si to jako: Batoh na výlet. Funkce je jako turista, který si do batohu (closure) zabalil věci z domova (vnější scope). I když je daleko od domova (scope už skončil), má pořád k věcem přístup.

Jak to funguje?

Closure = funkce + prostředí (scope), ve kterém byla vytvořená:

1. Vytvoříš funkci UVNITŘ jiné funkce (nebo bloku)
2. Vnitřní funkce odkazuje na proměnné z vnějšího scope
3. Vrátíš nebo předáš tuto vnitřní funkci PRYČ (z vnějšího scope)
4. Když ji později zavoláš, stále vidí ty vnější proměnné
5. To jsou "closured" proměnné - closure si je "pamatuje"

Důležité:

  • Closure není kopie proměnné, je to živé spojení (live link)
  • Každá instance funkce má vlastní closure
  • Closure funguje přes lexikální scope (kde je kód napsán)

Klíčové koncepty

  • Closure - funkce si pamatuje proměnné z vnějšího scope
  • Lexical scope + instance - closure je založen na tom, kde je funkce definovaná
  • Live link - closure ukazuje na skutečnou proměnnou, ne kopii
  • Private data - closure umožňuje data hiding
  • Function factory - funkce, která vytváří jiné funkce s closures

JavaScript

Příklad 1: Základní closure - counter

function createCounter() {
  let count = 0;  // Soukromá proměnná

  return function() {
    count++;      // Closure: vidí count z vnějšího scope!
    return count;
  };
}

const counterA = createCounter();
const counterB = createCounter();

console.log(counterA());  // → 1
console.log(counterA());  // → 2
console.log(counterB());  // → 1 (vlastní count!)
console.log(counterA());  // → 3

Co se stalo?

  • createCounter vytvoří lokální count
  • Vrátí funkci, která "zavírá nad" (closes over) count
  • Každé volání createCounter vytvoří nový count a novou funkci
  • counterA a counterB mají vlastní closures - vlastní count
  • Closure si pamatuje count i po tom, co createCounter skončí!

Příklad 2: Closure s parametrem

function makeMultiplier(factor) {
  return function(number) {
    return number * factor;  // Closure: pamatuje si factor
  };
}

const double = makeMultiplier(2);
const triple = makeMultiplier(3);

console.log(double(5));   // → 10 (5 * 2)
console.log(triple(5));   // → 15 (5 * 3)
console.log(double(10));  // → 20 (10 * 2)

Co se stalo?

  • double si "pamatuje" factor = 2
  • triple si "pamatuje" factor = 3
  • Každá instance má vlastní closure přes vlastní factor
function makeCounter() {
  let count = 0;

  return {
    increment: () => ++count,
    decrement: () => --count,
    getValue: () => count
  };
}

const counter = makeCounter();

console.log(counter.increment());  // → 1
console.log(counter.increment());  // → 2
console.log(counter.decrement());  // → 1
console.log(counter.getValue());   // → 1

Co se stalo?

  • Všechny tři funkce sdílejí STEJNÝ count (live link!)
  • increment zvýší count → všechny funkce vidí novou hodnotu
  • Není to kopie - je to odkaz na skutečnou proměnnou

Příklad 4: Soukromá data (data hiding)

function createBankAccount(initialBalance) {
  let balance = initialBalance;  // SOUKROMÉ - nedostupné zvenku!

  return {
    deposit(amount) {
      if (amount > 0) {
        balance += amount;
        return balance;
      }
      return "Invalid amount";
    },

    withdraw(amount) {
      if (amount > 0 && amount <= balance) {
        balance -= amount;
        return balance;
      }
      return "Invalid amount or insufficient funds";
    },

    getBalance() {
      return balance;
    }
  };
}

const account = createBankAccount(100);

console.log(account.getBalance());     // → 100
console.log(account.deposit(50));      // → 150
console.log(account.withdraw(30));     // → 120

// ❌ Nelze přistoupit k balance přímo!
// console.log(account.balance);  // → undefined
// balance = 9999999;  // Nelze! balance je soukromý

Co se stalo?

  • balance je soukromá - closure ji chrání
  • Jediný způsob přístupu jsou metody (deposit, withdraw, getBalance)
  • Nemůžeš "podvádět" a měnit balance přímo
  • To je encapsulation (zapouzdření) - důležitý programming pattern!

Příklad 5: Closure v event handlerech

function setupButton(buttonId, message) {
  const button = document.getElementById(buttonId);

  button.addEventListener("click", function() {
    console.log(message);  // Closure: pamatuje si message!
  });
}

setupButton("btn1", "Button 1 clicked!");
setupButton("btn2", "Button 2 clicked!");

// Když klikneš na btn1, vypíše: "Button 1 clicked!"
// Když klikneš na btn2, vypíše: "Button 2 clicked!"

Co se stalo?

  • Každý event handler si pamatuje svůj vlastní message přes closure
  • I když setupButton už dávno skončila, handler stále vidí message
  • To je velmi častý use case pro closures!

Příklad 6: Časté chyba - closure v cyklu s var

// ❌ ŠPATNĚ - všechny funkce sdílejí stejné i
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);  // Očekáváš 0, 1, 2
  }, 1000);
}
// → 3, 3, 3 (všechny vidí konečné i = 3!)

// ✅ DOBŘE - let vytváří nový scope pro každou iteraci
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);  // Každá funkce má vlastní i
  }, 1000);
}
// → 0, 1, 2 (každá funkce si pamatuje své i)

Co se stalo?

  • var má function scope - JEDNO i pro všechny iterace
  • Všechny closures ukazují na STEJNÉ i
  • Když cyklus skončí, i = 3 → všechny funkce vidí 3
  • let má block scope - NOVÉ i pro každou iteraci
  • Každá closure má vlastní i

TypeScript

TypeScript respektuje closures stejně jako JavaScript!

function createCounter(): () => number {
  let count: number = 0;

  return function(): number {
    count++;
    return count;
  };
}

const counter: () => number = createCounter();
console.log(counter());  // → 1

Typy v closures:

function makeMultiplier(factor: number): (n: number) => number {
  return (number: number): number => {
    return number * factor;  // Closure: factor je type number
  };
}

const double: (n: number) => number = makeMultiplier(2);
console.log(double(5));  // → 10

TypeScript přidává:

  • Typování closured proměnných - factor má typ number
  • Typování vrácených funkcí - jasně vidíš signaturu
  • IntelliSense - editor ví, co closure obsahuje

Rozdíl JS vs TS

JavaScript:

function makeAdder(x) {
  return function(y) {
    return x + y;  // x může být cokoli!
  };
}

const add5 = makeAdder(5);
const addHello = makeAdder("hello");

console.log(add5(10));       // → 15
console.log(addHello(" world"));  // → "hello world" (string concat!)

TypeScript:

function makeAdder(x: number): (y: number) => number {
  return function(y: number): number {
    return x + y;
  };
}

const add5 = makeAdder(5);
const addHello = makeAdder("hello");  // ❌ Error: Argument of type 'string' is not assignable to parameter of type 'number'

console.log(add5(10));  // → 15

Rozdíl:

  • JavaScript dovolí jakýkoliv typ v closure
  • TypeScript kontroluje typy closured proměnných
  • Bezpečnější - víš, co v closure máš

Tip

💡 Closure pro private data:

// ✅ Používej closure pro zapouzdření
function createUser(name, email) {
  // Soukromá data
  let password = null;

  return {
    getName: () => name,
    getEmail: () => email,
    setPassword: (newPassword) => {
      password = newPassword;  // Soukromé!
    },
    checkPassword: (inputPassword) => inputPassword === password
  };
}

const user = createUser("Alice", "alice@example.com");
user.setPassword("secret123");
console.log(user.checkPassword("secret123"));  // → true

// password není dostupný přímo!

💡 Používej let/const v cyklech pro closures:

// ❌ Špatně - var v cyklu
for (var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = function() {
    console.log(i);  // Všechny vidí konečné i!
  };
}

// ✅ Dobře - let v cyklu
for (let i = 0; i < buttons.length; i++) {
  buttons[i].onclick = function() {
    console.log(i);  // Každá má vlastní i
  };
}

💡 Closure není magie - sleduj scope:

function outer() {
  let x = 10;

  function inner() {
    console.log(x);  // Closure: vidí x z outer
  }

  return inner;
}

const fn = outer();
fn();  // → 10

// Closure funguje díky lexical scope!
// inner má přístup k x, protože je definovaná uvnitř outer

Kvíz

Co vypíše tento kód?

function test() {
  let x = 1;

  setTimeout(function() {
    console.log(x);
  }, 1000);

  x = 2;
}

test();

- Closure NENÍ snapshot (kopie), je to live link

- Closure ukazuje na skutečnou proměnnou x. Když se x změní na 2, closure vidí novou hodnotu

- Closure si pamatuje proměnné i po skončení funkce

- Closure zajišťuje, že x je stále dostupný

🎯 Závěrečný projekt

Po dokončení všech 8 dílů vytvoříte jednoduchou Todо aplikaci v čistém JavaScriptu. Naučíte se, jak aplikovat vše, co jste se naučili, na reálný projekt.

Zobrazit podrobnosti projektu →

Připraveni začít?

Zaregistrujte se a získejte přístup ke všem dílům tohoto seriálu

Kontaktujte mě

Informace o seriálu

Obtížnost

Délka

Cca 480 minut

Počet videí

8 videí + projekty

Certifikát

Po dokončení obdržíte certifikát


Lekce v této sekci


Struktura lekcí (souborový strom)

06. Typescript specifika
  • v přípravě
08. Moduly tridy
  • v přípravě
09. React zaklady
  • v přípravě
10. React hooks
  • v přípravě
12. Nextjs server
  • v přípravě
13. Databaze auth
  • v přípravě
14. Nextjs pokrocile
  • v přípravě