Lekce 0.2 · JavaScript obecně
Funkce a closures
Šipkové funkce, parametry, callbacky, funkce vracející funkce, closures a čisté funkce.
Načítám lekci…
Lekce 0.2 · JavaScript obecně
Šipkové funkce, parametry, callbacky, funkce vracející funkce, closures a čisté funkce.
Načítám lekci…
Funkce je jako kuchyňský robot: něco do něj vložíš (parametry), on s tím něco udělá a něco ti vrátí (návratová hodnota). Mixér dostane jahody a mléko a vrátí koktejl. Nemusíš vědět, jak uvnitř funguje motor – stačí vědět, co do něj dát a co z něj vyleze.
V JavaScriptu mají funkce ještě jednu vlastnost, která je pro React zásadní: funkce je hodnota, stejně jako číslo nebo text. Dá se uložit do proměnné, předat jiné funkci a dokonce z funkce vrátit. Na tom stojí skoro všechno, co v Reactu napíšeš.
// 1) Deklarace funkcefunction add(a, b) {return a + b}// 2) Funkční výraz – funkce uložená do proměnnéconst add = function (a, b) {return a + b}// 3) Šipková funkce (arrow function) – nejčastější v moderním kóduconst add = (a, b) => {return a + b}// 3b) Šipková funkce s jedním výrazem – return se nepíšeconst add = (a, b) => a + badd(2, 3) // 5 – voláš všechny stejně
const double = (n) => { return n * 2 } // plná podobaconst double = (n) => n * 2 // jeden výraz → bez { } a bez returnconst double = n => n * 2 // jeden parametr → závorky jdou vynechat// (většina kódu je ale píše vždy, pro přehlednost)const greet = () => 'Ahoj' // bez parametrů → prázdné závorky jsou povinnéconst log = (a, b) => console.log(a, b) // víc parametrů → závorky povinné
| function | šipková funkce | |
|---|---|---|
| Kde ji uvidíš | komponenty, pojmenované pomocné funkce | callbacky, krátké funkce, obsluhy událostí |
| Jde zavolat dřív, než je v kódu napsaná? | ano (hoisting) | ne |
this | vlastní (podle toho, kdo ji volá) | přebírá z okolí – v moderním kódu to skoro neřešíš |
JavaScript nehlídá, kolik argumentů funkci předáš:
function greet(name, greeting) {return `${greeting}, ${name}!`}greet('Jana', 'Ahoj') // 'Ahoj, Jana!'greet('Jana') // 'undefined, Jana!' – chybějící parametr je undefinedgreet('Jana', 'Ahoj', 'navíc') // 'Ahoj, Jana!' – přebytečný se tiše zahodí
function greet(name, greeting = 'Ahoj') {return `${greeting}, ${name}!`}greet('Jana') // 'Ahoj, Jana!'greet('Jana', 'Čau') // 'Čau, Jana!'greet('Jana', undefined) // 'Ahoj, Jana!' – výchozí hodnota se použije i pro undefinedgreet('Jana', null) // 'null, Jana!' – ale pro null ne!
Tři tečky před posledním parametrem posbírají všechny zbylé argumenty do pole:
function sum(...numbers) {let total = 0for (const n of numbers) { // for…of projde pole položku po položcetotal += n}return total}sum(1, 2) // 3 – numbers je [1, 2]sum(1, 2, 3, 4) // 10 – numbers je [1, 2, 3, 4]sum() // 0 – numbers je []function log(level, ...messages) { // první argument zvlášť, zbytek do poleconsole.log(`[${level}]`, messages.join(' '))}log('INFO', 'Uživatel', 'se', 'přihlásil') // [INFO] Uživatel se přihlásil
Když má funkce víc parametrů, snadno se splete pořadí. Řešení: předat jeden objekt a rovnou ho v parametru rozbalit (destrukturovat). Přesně takhle fungují props v Reactu.
// ❌ Co znamená true a false? Musíš se podívat do definice.createUser('Jana', 31, true, false)// ✅ Pojmenované „parametry“ – na pořadí nezáležífunction createUser({ name, age, isAdmin = false, newsletter = true }) {return { name, age, isAdmin, newsletter }}createUser({ name: 'Jana', age: 31, isAdmin: true })
function add(a, b) {return a + b // return funkci ukončí a vrátí hodnotuconsole.log('nikdy se neprovede')}function logIt(x) {console.log(x) // bez return…}const result = logIt(5) // …vrací funkce undefined
// ❌ Hluboké zanořenífunction getDiscount(user) {if (user) {if (user.isMember) {if (user.years > 5) {return 20} else {return 10}}}return 0}// ✅ Early return – nejdřív odbav výjimky, pak hlavní případfunction getDiscount(user) {if (!user) return 0if (!user.isMember) return 0if (user.years > 5) return 20return 10}
function minMax(numbers) {return { min: Math.min(...numbers), max: Math.max(...numbers) } // objekt…}const { min, max } = minMax([3, 9, 1]) // …a rozbalitfunction useToggle() {return [value, toggle] // nebo pole – takhle vrací hodnoty useState}const [isOpen, toggleOpen] = useToggle()
Funkce je hodnota jako každá jiná. Rozlišuj funkci samotnou (greet) a výsledek jejího zavolání (greet()):
function greet() {return 'Ahoj'}greet // ƒ greet() – funkce samotná (recept)greet() // 'Ahoj' – výsledek zavolání (hotové jídlo)const hello = greet // druhé jméno pro TUTÉŽ funkci, nic se nevoláhello() // 'Ahoj'
Když funkci předáš jako argument, říká se jí callback („zavolej mi zpátky“). Ta druhá funkce ji zavolá, až bude potřeba – třeba za sekundu, nebo pro každou položku pole.
function sayHi() {console.log('Ahoj!')}setTimeout(sayHi, 1000) // ✅ předáš funkci – setTimeout ji zavolá za 1 ssetTimeout(sayHi(), 1000) // ❌ zavoláš ji HNED a setTimeoutu předáš její výsledek (undefined)setTimeout(() => console.log('Ahoj!'), 1000) // ✅ callback napsaný rovnou na místě[1, 2, 3].map((n) => n * 2) // map zavolá callback pro každou položku → [2, 4, 6]button.addEventListener('click', () => alert('Klik!')) // zavolá se při každém kliknutí
function repeat(times, action) {for (let i = 0; i < times; i++) {action(i) // zavolá předanou funkci}}repeat(3, (i) => console.log('Kolo', i)) // Kolo 0, Kolo 1, Kolo 2function measure(fn) {const start = performance.now()fn()console.log(`Trvalo ${performance.now() - start} ms`)}measure(() => heavyCalculation())
Funkce může funkci i vyrobit a vrátit. Takové „továrně“ se říká funkce vyššího řádu:
function makeGreeter(greeting) {return (name) => `${greeting}, ${name}!`}const czech = makeGreeter('Ahoj') // czech je teď FUNKCEconst english = makeGreeter('Hello')czech('Jano') // 'Ahoj, Jano!'english('Jane') // 'Hello, Jane!'makeGreeter('Čau')('Petře') // 'Čau, Petře!' – vyrobit a hned zavolat// totéž kratšíconst makeGreeter = (greeting) => (name) => `${greeting}, ${name}!`
Všimni si, že funkce czech výše zná greeting, přestože makeGreeter už dávno skončila. Jak je to možné?
Každá funkce si při vzniku nabalí batůžek s proměnnými z místa, kde vznikla. Když ji pak zavoláš kdekoli jinde, sáhne do batůžku. Tomu batůžku se říká closure (uzávěr).
function makeCounter() {let count = 0 // tahle proměnná putuje do batůžkureturn {increment: () => { count++ },get: () => count,}}const a = makeCounter()const b = makeCounter() // nové zavolání = nový batůžek s vlastním counta.increment()a.increment()b.increment()a.get() // 2b.get() // 1 – počítadla se neovlivňujícount // ❌ ReferenceError – zvenku se k count nedostaneš, je „soukromá“
Co vidíš: Tlačítko, které zavolá obyčejnou JS funkci makeCounter(). Každé zavolání vyrobí nové počítadlo s vlastní proměnnou count v closure.
Vyzkoušej:
count.Co z toho plyne: Každé zavolání funkce vytvoří nové proměnné. Funkce, které při tom vzniknou, si je nesou s sebou – i dlouho poté, co vnější funkce skončila.
Načítám interaktivní část…
let message = 'Ahoj'const show = () => console.log(message)message = 'Nazdar'show() // 'Nazdar' – funkce čte proměnnou v okamžiku zavolání
V Reactu to má zajímavý důsledek: každý render komponenty je nové zavolání funkce s novými proměnnými. Funkce vytvořená v jednom renderu „vidí“ hodnoty právě z toho renderu. Tomu se říká snímek (snapshot) a podrobně ho rozebírá lekce 0.10 a lekce o stavu.
Čistá funkce je jako kalkulačka: pro stejné vstupy vrátí vždy stejný výsledek a kromě toho nic jiného nedělá. Nemění nic venku, nečte hodiny ani náhodu.
// ✅ Čistéconst add = (a, b) => a + bconst fullName = (user) => `${user.first} ${user.last}`const withVat = (price) => Math.round(price * 1.21)// ❌ Nečisté – výsledek závisí na něčem jiném než na vstupuconst now = () => new Date() // pokaždé jiný časconst roll = () => Math.random() // pokaždé jiné čísloconst total = (price) => price + tax // čte vnější proměnnou, která se může změnit// ❌ Nečisté – mají vedlejší efekt (mění svět kolem)let count = 0const increment = () => { count++ } // mění vnější proměnnouconst addItem = (list, x) => { list.push(x) } // mění předané poleconst save = (data) => localStorage.setItem('data', data)const log = (x) => console.log(x)
Doplň těla dvou šipkových funkcí. Každá se vejde na jeden řádek.
VÝZVA: Dvě šipkové funkce 1. square(3) má vrátit 9 (číslo krát samo sebe). 2. isEven(4) má vrátit true, isEven(5) false. Sudé číslo má zbytek po dělení dvěma 0: n % 2 === 0
Načítám interaktivní část…
Šest malých funkcí, které vyrábějí jiné funkce nebo je dostávají jako argument. Procvičíš si parametry, callbacky a closures.
Střední a těžké výzvy jsou bonus pro přihlášené. Registrace je zdarma – stačí e-mail, žádné heslo.
return. Objekt vracej v ({ … }).undefined, výchozí hodnoty přes =, libovolný počet přes ...rest. Hodně parametrů → jeden objekt.fn je funkce, fn() je její výsledek. Callback předávej bez závorek, nebo ho obal do () => fn(arg).Checks, Check)Checks, Check – ze souboru src/course/checks.tsx. Pomocník kurzu pro výzvy s funkcemi: spustí testy a ukáže ✅/❌. Jak funguje uvnitř, nemusíš řešit – stačí, že ti řekne, které kroky už máš hotové./** One test: a description and a function returning true (passed) or false. It may be async. */export type Check = [name: string, test: () => boolean | Promise<boolean>]/*** Runs the tests of a challenge and shows ✅ or ❌ for each.* An error thrown in a test (e.g. when trying to change a frozen object) is shown under the test.*/export function Checks({ tests }: { tests: Check[] }) {const t = useT()const [results, setResults] = useState<Result[]>(() => tests.map(() => ({ status: 'pending' })))useEffect(() => {let ignore = falsetests.forEach(([, test], i) => {Promise.resolve().then(test).then((ok): Result => (ok ? { status: 'ok' } : { status: 'fail' }),(e): Result => ({ status: 'fail', error: e instanceof Error ? e.message : String(e) }),).then((result) => {if (ignore) returnsetResults((prev) => prev.map((r, idx) => (idx === i ? result : r)))})})return () => {ignore = true}}, [tests])const passed = results.filter((r) => r.status === 'ok').lengthreturn (<div className="stack"><strong className={passed === tests.length ? 'ok' : undefined}>{t.checksPassed(passed, tests.length)}</strong><ul className="list">{tests.map(([name], i) => {const r = results[i] ?? { status: 'pending' }return (<li key={name}>{r.status === 'pending' ? '⏳' : r.status === 'ok' ? '✅' : '❌'} {name}{r.status === 'fail' && r.error && (<div className="bad muted">{t.checksError} {r.error}</div>)}</li>)})}</ul></div>)}