⚡ BI-PJS – Programování v JavaScriptu

Státnicové poznámky · Bakalářské státní zkoušky · FIT ČVUT

OOP & Prototype Dědičnost & Class Event Loop Async / Await
Okruh 1

Objektově orientované programování v JavaScriptu

Prototype chain, klíčové slovo this, konstruktory, třídy (class), dědičnost, mixiny a kompozice. Pochopení toho, jak JS implementuje OOP jinak než klasické jazyky jako Java.

🔗 Prototype a prototypový řetězec

Prototypový řetězec (prototype chain) – mechanismus, pomocí něhož JavaScript implementuje dědičnost. Každý objekt má interní odkaz [[Prototype]] na jiný objekt (nebo null). Při přístupu k vlastnosti se JS postupně „dívá nahoru" po řetězci, dokud vlastnost nenajde nebo nenarazí na null.

JavaScript není class-based jazyk v klasickém smyslu – pod kapotou je to prototype-based dědičnost. Klíčové slovo class (ES6+) je pouze syntaktický cukr nad prototypy.

  • Vlastní vlastnosti (own properties) – definovány přímo na objektu (this.name = ...)
  • Zděděné vlastnosti (inherited properties) – přístupné přes __proto__, ale nepatří přímo objektu
  • __proto__ – přímý přístup na prototype chain (zastaralé, preferuj Object.getPrototypeOf() / Object.setPrototypeOf())
Příklad: nastavení __proto__
const obj1 = { a: 1 }
const obj2 = { b: 2 }
obj1.__proto__ = obj2
// `a` je vlastní vlastnost obj1
// `b` je dostupné přes __proto__ (zděděné)

Proč to tak je? JS byl navržen jako lehký, flexibilní jazyk. Místo pevné třídní hierarchie (jako Java) umožňuje objektům dědit přímo od jiných objektů – to je výkonnější a flexibilnější pro dynamické prostředí browseru.

Vyhledávání vlastnosti (property lookup)

  1. Existuje vlastnost jako own property na objektu?
  2. Má objekt __proto__? → hledej na __proto__
  3. __proto__ také svůj __proto__? → pokračuj nahoru
  4. Dosáhl jsi null? → vlastnost neexistuje (undefined)
user.toString()
// 1. toString() na user? Ne
// 2. user.__proto__ = User.prototype → toString()? Ne
// 3. User.prototype.__proto__ = Object.prototype → toString()? ANO ✓

🏗️ Konstruktory, new a Factory.prototype

Konstruktorová funkce – běžná funkce volaná s operátorem new. JS automaticky: (1) vytvoří prázdný objekt, (2) nastaví jeho [[Prototype]] na Factory.prototype, (3) přiřadí tento objekt jako this, (4) vrátí this (pokud funkce nevrátí jiný objekt).

Evoluce přístupu k vytváření objektů se sdílenými metodami:

❌ Problém – kopírování metod pro každý objekt
function User(name) {
  const user = {
    name,
    sayHi: function() { return this.name }
  }
  return user
}
// Pro 1000 uživatelů vznikne 1000 kopií sayHi() → plýtvání pamětí!
✅ Správné řešení – sdílené metody přes prototype
function User(name) {
  // this = {}
  // this.__proto__ = User.prototype
  // this.constructor = User
  this.name = name
  // return this  ← implicitně
}
User.prototype = {
  sayHi: function() { return this.name }
}
const user = new User('Pepa')

Klíčový vztah: instance.__proto__ === Factory.prototype — to platí vždy, např. [].__proto__ === Array.prototype.

Object.create() jako alternativa
const commonMethods = { sayHi() { return this.name } }
function User(name) {
  // Vytvoří objekt s daným prototypem, bez new
  const user = Object.create(commonMethods, {
    name: { value: name }
  })
  return user
}
// Problém: chybí .constructor, nefunguje instanceof správně
PřístupVýhodyNevýhody
Kopírování metod do každého objektuJednoduchostPaměťová neefektivita
__proto__ mutace po vytvořeníSdílení metodBlokuje engine optimalizace (hidden classes)
Object.create(proto)Bez mutace, výkonnéChybí constructor, broken instanceof
Konstruktor + new + .prototypeIdiomatické, výkonné, instanceof fungujeVíce boilerplate kódu
class (ES6+)Čistá syntax, private fieldsJen syntactic sugar – stále prototype pod kapotou

👉 Klíčové slovo this

this – kontext volání funkce. Hodnota this není určena při definici funkce, ale při jejím volání. Výjimkou jsou arrow funkce, které this lexikálně přebírají z okolního scope.

Hodnota this závisí na způsobu volání funkce:

Způsob voláníHodnota this
func() – přímé voláníundefined (strict mode) / globální objekt (window)
obj.method() – metoda objektuobj
new Func() – konstruktornově vytvořený objekt
func.call(ctx, ...args)ctx (explicitně)
func.apply(ctx, [args])ctx (explicitně)
func.bind(ctx)ctx (pevně navázáno)
Arrow funkce () => {}Lexikální this z okolního scope
⚠️ Častá chyba – ztráta kontextu this
class Timer {
  constructor() { this.count = 0 }
  start() {
    // Zde this = undefined nebo window, NE Timer instance!
    setTimeout(function() { this.count++ }, 1000)
    // Řešení 1: arrow funkce
    setTimeout(() => { this.count++ }, 1000)
    // Řešení 2: bind
    setTimeout(function() { this.count++ }.bind(this), 1000)
  }
}

Proč arrow funkce nemá vlastní this? Arrow funkce byly zavedeny v ES6 právě proto, aby vyřešily problém se ztrátou kontextu v callbackech. Jejich this je zachyceno lexikálně v okamžiku definice.

🧬 Prototypová dědičnost (bez class)

Pro zdědění vlastních vlastností i prototypálních metod je potřeba kombinovat dva přístupy:

❌ Špatný přístup – sdílení stejného prototype objektu
FootballPlayer.prototype = Human.prototype
FootballPlayer.prototype.sayTeam = function() { return this.team }
// ❌ Přepíše i Human.prototype! Všechny Human instance by získaly sayTeam.
✅ Správný přístup – Object.create + call
function Human(name) {
  this.name = name
}
Human.prototype = {
  sayName() { return this.name }
}

function FootballPlayer(name, team) {
  Human.call(this, name)  // ← zdědění VLASTNÍCH vlastností
  this.team = team
}
// Nový objekt s Human.prototype jako __proto__, mutace se nešíří nahoru
FootballPlayer.prototype = Object.create(Human.prototype)
FootballPlayer.prototype.sayTeam = function() { return this.team }

Proč Object.create a ne new Human()? new Human() by spustilo konstruktor Human (nežádoucí vedlejší efekty). Object.create(Human.prototype) vytvoří objekt s daným prototypem bez spuštění konstruktoru.

Zjednodušený polyfill pro Object.create
Object.create = Object.create || function(proto) {
  const F = function() {}
  F.prototype = proto
  return new F()
  // Vytvoří prázdný objekt, jehož __proto__ je proto
}

Shrnutí kroků pro správnou prototypovou dědičnost:

  1. Vlastní vlastnosti: ParentConstructor.call(this, ...args) uvnitř child konstruktoru
  2. Prototypální metody: Child.prototype = Object.create(Parent.prototype)
  3. Přidat child-specifické metody: Child.prototype.newMethod = ...

🏛️ Syntaxe class (ES6+)

class – syntaktický cukr nad prototypovou dědičností. typeof Human === 'function' – třídy jsou stále funkce. Metody definované uvnitř class jsou přidány na ClassName.prototype, ne přímo na instance.
Ekvivalence class a prototype kódu
class Human {
  constructor(name) { this.name = name }
  sayName() { return this.name }
}
class FootballPlayer extends Human {
  constructor(name, team) {
    super(name)   // ≡ Human.call(this, name)
    this.team = team
  }
  sayTeam() { return this.team }
}
// extends Human  ≡  Object.create(Human.prototype)
// super(name)    ≡  Human.call(this, name)
// Metody jsou stále na Human.prototype / FootballPlayer.prototype

Klíčová pravidla pro class:

  • super() musí být voláno v child konstruktoru před použitím this
  • Třídy nemají hoisting jako funkce (temporal dead zone)
  • class tělo je vždy ve strict mode
  • typeof MyClass === 'function' – je to funkce!

Private fields (#)

Soukromá pole (#fieldName) – vlastnosti přístupné pouze uvnitř dané třídy. Nejde je číst ani nastavit zvenčí (ani z odvozených tříd). Jsou skutečně privátní na úrovni jazyka, na rozdíl od konvence _name.
class Human {
  #age
  constructor() { this.#age = 30 }
  isAdult() { return this.#age > 18 }
}
const user = new Human()
console.log(user.isAdult()) // true
console.log(user.#age)      // ❌ SyntaxError

🧩 Mixiny a kompozice

Mixin – vzor, kdy objekt nebo třída „zapůjčuje" metody z jiného zdroje bez formálního dědění. Řeší problém JS, kde lze dědit jen od jednoho rodiče.

Existují dva hlavní přístupy:

1. Class mixins (funkce vracející třídu)

const WithTimestamps = Base => class extends Base {
  constructor(...args) {
    super(...args)
    this.createdAt = new Date()
    this.updatedAt = new Date()
  }
  touch() { this.updatedAt = new Date() }
}
const WithId = Base => class extends Base {
  constructor(...args) {
    super(...args)
    this.id = crypto.randomUUID()
  }
}
class Model {}
class User extends WithTimestamps(WithId(Model)) {
  constructor(name) { super(); this.name = name }
}
const u = new User("Petr")
u.touch()

2. Kompozice objektů (bez dědičnosti)

const asNamed = name => ({ name })
const asTimestamps = () => {
  let createdAt = new Date(), updatedAt = new Date()
  return {
    get createdAt() { return createdAt },
    get updatedAt() { return updatedAt },
    touch() { updatedAt = new Date() }
  }
}
const createUser = name => Object.assign({}, asNamed(name), asTimestamps())
const user = createUser('Petr')
user.touch()
Dědičnost vs. Kompozice

Dědičnost: „is-a" vztah (FootballPlayer is-a Human). Vhodná pro jasné taxonomie.

Kompozice: „has-a" vztah (User has Timestamps, has Id). Flexibilnější, méně provázaná.

Obecné doporučení: „Prefer composition over inheritance" – kompozice je obvykle čitelnější a lépe rozšiřitelná.

🧊 Kopírování objektů a jejich zmrazení

structuredClone()

structuredClone(obj) – vytvoří hlubokou kopii objektu. Kopíruje pouze vlastní, enumerable vlastnosti. Nesmí obsahovat funkce (vyhodí chybu). Nezdědí nic z prototype chain.
const o1 = { a: 1 }
const o2 = Object.create(o1, {
  b: { value: 2, enumerable: true  },  // ✅ bude zkopírováno
  c: { value: 3, enumerable: false },  // ❌ non-enumerable → ne
  f: { value: () => {}, enumerable: false } // ❌ funkce → chyba pokud enumerable
})
const copy = structuredClone(o2)
console.log(copy) // { b: 2 }
// `a` chybí (inherited), `c` a `f` chybí (non-enumerable / funkce)

Object.freeze / seal / preventExtensions

MetodaBlokuje přidání propsBlokuje změnu propsBlokuje konfiguraci propsBlokuje změnu proto
preventExtensions()
seal()
freeze()

Poznámka: všechny tyto metody jsou mělké (shallow) – vnořené objekty zůstávají modifikovatelné.

Proxy objects

Proxy – wrapper kolem objektu, který umožňuje zachytit (interceptovat) operace jako čtení, zápis, volání funkcí, in operátor, atd. pomocí tzv. traps (get, set, has, deleteProperty, ...).
const handler = {
  get(target, prop) {
    console.log(`Čtení vlastnosti: ${prop}`)
    return target[prop]
  }
}
const proxy = new Proxy({ name: 'Pepa' }, handler)
proxy.name // → "Čtení vlastnosti: name"

🔍 Iterace přes vlastnosti a výkonnostní optimalizace

Získání všech vlastností (včetně zděděných)

// Imperativní styl
function getAllProps(obj) {
  const props = Object.getOwnPropertyNames(obj)
  while (obj = Object.getPrototypeOf(obj)) {
    props.push(...Object.getOwnPropertyNames(obj))
  }
  return props
}
// Funkcionální styl (rekurzivní)
function getAllProps(obj) {
  const proto = Object.getPrototypeOf(obj)
  const props = Object.getOwnPropertyNames(obj)
  return proto ? [...props, ...getAllProps(proto)] : props
}
MetodaCo vrací
Object.keys(obj)Vlastní enumerable string klíče
Object.getOwnPropertyNames(obj)Vlastní string klíče (i non-enumerable)
for...inVšechny enumerable klíče (i zděděné)
Object.getPrototypeOf(obj)Odkaz na prototype objektu

Hidden Classes (skryté třídy)

Hidden classes – interní optimalizace JS enginů (V8, SpiderMonkey). Engine přiřadí každému objektu „skrytou třídu" popisující strukturu vlastností. Pokud mají objekty stejnou strukturu (stejné pořadí přidávání vlastností), engine může používat sdílenou hidden class a optimalizovat přístup k vlastnostem.

Proto se nedoporučuje:

  • Dynamicky přidávat vlastnosti objektu po vytvoření v různém pořadí
  • Mutovat __proto__ po vytvoření objektu
  • Mazat vlastnosti (delete obj.prop) – způsobí přechod na pomalejší hidden class
Okruh 2

Asynchronní programování v JavaScriptu

Event loop, call stack, fronty úloh (micro/macro queue), callbacky, Promises, async/await, generátory, AbortController, debounce/throttle a asynchronní iterátory.

⚙️ Asynchronicita vs. multithreading

Asynchronicita – model zpracování úloh, kde JS engine nečeká na dokončení pomalé operace (I/O, síť, timer), ale pokračuje v provádění dalšího kódu. Po dokončení operace je callback zařazen do fronty.
Multithreading – více vláken (threads) běžících skutečně paralelně. JS je single-threaded (jedno vlákno), ale runtime a Web APIs mohou interně využívat vlákna (např. síťový požadavek, worker threads v Node.js, Web Workers v browseru).
Klíčový rozdíl

JavaScript sám běží v jednom vlákně – v daný okamžik se provádí vždy jen jeden kus kódu. Asynchronicita NENÍ multithreading. JS deleguje pomalé operace na runtime (browser/Node.js), který může využít vlákna, a po dokončení operace zařadí callback do fronty.

🔄 Event Loop – srdce JS asynchronicity

Event Loop – nekonečná smyčka, která sleduje call stack a fronty úloh. Pokud je call stack prázdný, přesune event loop úlohu z fronty na call stack k provedení.

Komponenty systému:

  • Call Stack (zásobník volání) – LIFO struktura uchovávající prováděné funkce. Každé volání funkce přidá frame na zásobník, návrat ho odebere.
  • Web APIs / Node.js APIs – prostředí runtime obsluhující asynchronní operace (setTimeout, fetch, DOM events, fs.readFile...). Mohou využívat interně vlákna.
  • Callback Queue / Task Queue (macro queue) – fronta callbacků čekajících na zpracování (z setTimeout, DOM events, fetch...).
  • Microtask Queue – prioritní fronta pro mikrotasky (Promise.then, queueMicrotask, MutationObserver).
  • Event Loop – vybírá úlohy z front a vkládá je do call stacku.
Pořadí zpracování jednoho „ticku"
  1. Proveď aktuální synchronní kód (call stack).
  2. Zpracuj všechny microtasky z microtask queue (včetně těch nově přidaných).
  3. Proveď jeden macrotask z macro queue (callback queue).
  4. Znovu zpracuj všechny microtasky.
  5. Příp. proveď render (v browseru).
  6. Opakuj.

Micro queue vs. Macro queue

Micro queue (vyšší priorita)Macro queue (nižší priorita)
Promise.then / .catch / .finallysetTimeout(...)
queueMicrotask(...)setInterval(...)
MutationObserverMessageChannel
process.nextTick() (Node.js, nejprioritnější)window.postMessage(...)
DOM events (click, keydown, load...)
Network I/O (fetch/XHR events, WebSocket)
requestAnimationFrame(...)
requestIdleCallback(...)
⚠️ process.nextTick() – Node.js only

process.nextTick() je specifické pro Node.js a má nejvyšší prioritu – je zpracováno ještě před ostatními microtasky. Pokud se zavolá rekurzivně, může zablokovat zpracování I/O.

requestAnimationFrame a requestIdleCallback

  • requestAnimationFrame(cb) – spustí callback před dalším překreslením obrazovky (typicky 60× za sekundu). Ideální pro animace – eliminuje „trhání" oproti setTimeout.
  • requestIdleCallback(cb) – spustí callback v době, kdy browser nemá jinou práci (idle time). Vhodné pro méně prioritní úkoly (analytika, prefetching). Negarantuje čas spuštění.

😱 Callback Hell – problém klasických callbacků

Callback Hell (Pyramid of Doom) – antipattern vznikající při řetězení asynchronních operací pomocí vnořených callbacků. Kód se stává nečitelným, těžko laditelným a chybovým vzorem pro ošetření chyb je opakující se boilerplate.
getUser(id, (err, user) => {
  if (err) return handleError(err)
  getSettings(user.id, (err, settings) => {
    if (err) return handleError(err)
    fetchData(settings.endpoint, (err, data) => {
      if (err) return handleError(err)
      transform(data, (err, result) => {
        // ... stále hlubší a hlubší vnoření → callback hell
      })
    })
  })
})

Problémy: nečitelnost, opakované if (err) return handleError(err), složité sdílení proměnných mezi úrovněmi.

🤝 Promise – objekt zapouzdřující asynchronní hodnotu

Promise – objekt reprezentující výsledek asynchronní operace. Může být v jednom ze tří stavů: pending (čeká), fulfilled (splněn s hodnotou), rejected (odmítnut s chybou). Stav je neměnný – jakmile je Promise fulfilled/rejected, nelze to změnit.

Interní struktura Promise:

Promise {
  [[State]]   = "pending" | "fulfilled" | "rejected"
  [[Result]]  = undefined | hodnota | error
  [[FulfillQ]] = []  // fronty callbacků
  [[RejectQ]]  = []
}

Vytvoření Promise

function get(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) resolve(xhr.responseText)
        else reject(xhr.status)
      }
    }
    xhr.send()
  })
}

Promise chaining (.then, .catch, .finally)

get('/api/user')
  .then(user => get(`/api/permissions?uid=${user.id}`))
  .then(perms => get(`/api/token?scopes=${perms.scopes.join(",")}`))
  .then(token => get(`/api/report?token=${token.token}`))
  .then(report => console.log("done:", report))
  .catch(err => console.error('chyba:', err))
  .finally(() => console.log('vždy se spustí'))

Proč vrácením Promise z .then dostaneme chain? Metoda .then() vždy vrátí nový Promise. Pokud callback vrátí Promise, nový Promise „přijme" jeho výsledek (unwrapping). Tím vzniká lineární chain místo vnoření.

Statické metody Promise

MetodaChováníKdy použít
Promise.resolve(val)Vrátí splněný Promise s hodnotouZabalení synchronní hodnoty
Promise.reject(err)Vrátí odmítnutý Promise s chybouRychlé odmítnutí
Promise.all([...])Čeká na všechny; odmítne při první chyběParalelní operace, potřebuju všechny výsledky
Promise.allSettled([...])Čeká na všechny, i chybné; vrátí status+valuePotřebuju výsledky i chybné operace
Promise.any([...])Splní se při první úspěšné; chybí pokud všechny selžouRace – chci první úspěch
Promise.race([...])Splní/odmítne při první dokončené (i chybné)Timeout pattern
Promise.all pro řešení scope problému
// Problém: user je mimo scope v pozdějším .then()
const userP = get('/api/user')
const permsP = userP.then(user => get(`/api/permissions?uid=${user.id}`))
const tokenP = Promise.all([userP, permsP]).then(([user, perms]) =>
  get(`/api/token?uid=${user.id}&scopes=${perms.scopes.join(",")}`)
)
Promise.all([userP, permsP, tokenP]).then(([user, perms, token]) => {
  console.log("done:", { user, perms, token })
})

Generátory – „líné" funkce s yield

Generátor (Generator) – speciální druh funkce (function*), která může svůj výpočet pozastavit (yield) a předat kontrolu volajícímu. Vrátí iterator objekt s metodami next(value), return(value), throw(error).

Klíčové vlastnosti:

  • Funkce function* při zavolání okamžitě nespustí tělo – vrátí iterator.
  • Každé volání generator.next(value) pokračuje až do dalšího yield.
  • Hodnota předaná do next(value) se stane hodnotou výrazu yield uvnitř funkce.
  • Generátor je dokončen, když dosáhne return nebo konce funkce ({ value: ..., done: true }).
function* counter() {
  let i = 0
  while (true) {
    const reset = yield i  // pozastavit, předat i volajícímu
    if (reset) i = 0
    else i++
  }
}
const gen = counter()
gen.next()    // { value: 0, done: false }
gen.next()    // { value: 1, done: false }
gen.next(true) // { value: 0, done: false } – reset!

Generátory jako základ pro async/await

Historicky byl async/await implementován pomocí generátorů a „runneru". Generátor pozastaví výpočet u yield promise, runner počká na splnění promise a výsledek předá zpět přes next(result):

function* main() {
  const status = yield fetch('https://api.example.com/data')
  const json = yield status.json()
  console.log(json)
}
function run(gen) {
  const g = gen()
  ;(function loop(value) {
    const next = g.next(value)
    if (!next.done) next.value.then(result => loop(result))
  })()
}
run(main)

async / await – syntaktický cukr nad Promises

async function – funkce, která vždy vrátí Promise. I pokud vrátí synchronní hodnotu, bude zabalena do Promise.resolve().
await – operátor použitelný pouze uvnitř async funkce. Pozastaví vykonávání async funkce dokud se Promise nevyřeší, ale neblokuje call stack – ostatní kód může běžet dál.
async function loadData() {
  try {
    const response = await fetch('/api/data')   // pozastaví loadData()
    const data = await response.json()
    return data  // zabaleno do Promise.resolve(data)
  } catch (e) {
    console.error('Chyba:', e)
    // await na rejected Promise hodí výjimku → zachytíme try/catch
  }
}
// Ekvivalent generátoru z předchozí sekce!
⚠️ try/catch je nutný pro await

Pokud await dostane odmítnutý Promise, hodí výjimku (throw). Bez try/catch bude chyba unhandled rejection. Alternativa: .catch() na vráceném Promise.

Paralelní vs. sekvenční await

// ❌ SEKVENČNÍ – každý čeká na předchozí (pomalé!)
const a = await fetchA()
const b = await fetchB()

// ✅ PARALELNÍ – oba požadavky spuštěny najednou
const [a, b] = await Promise.all([fetchA(), fetchB()])

Top-level await (ES2022)

V modulech (type="module") lze používat await na nejvyšší úrovni bez obalení do async funkce:

// module.js
const data = await fetch('/api/config').then(r => r.json())

🛑 AbortController & AbortSignal

AbortController – API pro zrušení (abort) asynchronních operací. Obsahuje metodu abort() a vlastnost signal (AbortSignal), která se předává do operace (fetch, addEventListener...).
const controller = new AbortController()
const btn = document.querySelector('#cancel')
btn.addEventListener('click', () => controller.abort())

try {
  const res = await fetch('/api/slow', { signal: controller.signal })
  const data = await res.json()
  console.log('ok', data)
} catch (e) {
  if (e.name === 'AbortError') console.log('Požadavek zrušen')
  else throw e  // jiná chyba – znovu hodit
}

AbortSignal lze propojit i s jinými API: addEventListener('event', handler, { signal }) – při abortu se listener automaticky odstraní.

🌊 Asynchronní iterátory a for await...of

Asynchronní iterátor – objekt implementující protokol [Symbol.asyncIterator], jehož next() metoda vrací Promise s { value, done }.
async function* asyncNumbers() {
  yield 1
  await new Promise(r => setTimeout(r, 100))
  yield 2
  yield 3
}

;(async () => {
  for await (const num of asyncNumbers()) {
    console.log(num)  // 1, pak 2, pak 3
    // break → zavře iterátor (spustí return())
  }
})()

Praktické využití: čtení streamů (ReadableStream), postupné zpracování výsledků z databáze, Server-Sent Events.

async function* (asynchronní generátor)

Kombinace async a function* – může používat jak await, tak yield. Vhodné pro produkci sekvencí hodnot, kde každá může být asynchronně získána.

⏱️ Debounce a Throttle

Debounce – odloží volání funkce o zadanou dobu po poslední události. Pokud přijde nová událost před uplynutím doby, timer se resetuje. Vhodné pro: vyhledávací input (čekej na pauzu), resize handler.
Throttle – omezí frekvenci volání funkce – zavolá ji nejvýše jednou za zadaný interval. Vhodné pro: scroll handler, mousemove, resize (pravidelné vzorkování).
// Debounce – volej až po pauze
const debounce = (fn, wait = 200) => {
  let timer
  return (...args) => {
    clearTimeout(timer)               // zruš předchozí timer
    timer = setTimeout(() => fn(...args), wait)  // naplánuj nový
  }
}
window.addEventListener('input', debounce(e => search(e.target.value), 250))
// Throttle – volej maximálně jednou za interval
const throttle = (fn, wait = 100) => {
  let locked = false
  return (...args) => {
    if (locked) return           // blokováno – ignoruj
    locked = true
    fn(...args)                  // okamžité volání
    setTimeout(() => { locked = false }, wait)  // uvolni po wait ms
  }
}
window.addEventListener('scroll', throttle(() => updateUI(), 100))
VzorKdy se funkce zavoláTypické použití
DebouncePo poslední události + čekací dobaSearch input, form validation
ThrottleMaximálně 1× za intervalScroll, resize, mousemove