JavaScript 02 · JavaScript obecně
Funkce a closures
Šipkové funkce, parametry, callbacky, funkce vracející funkce, closures a čisté funkce.
Načítám lekci…
JavaScript 02 · 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!'
Vyrobenou funkci nemusíš ukládat do proměnné, můžeš ji zavolat hned. Zápis se dvěma páry závorek za sebou vypadá zvláštně, ale čte se prostě zleva doprava: první závorky zavolají makeGreeter a ta vrátí funkci, druhé závorky zavolají tu vrácenou funkci.
makeGreeter('Čau')('Petře') // 'Čau, Petře!'// totéž ve dvou krocích:const pozdrav = makeGreeter('Čau') // 1. vyrob funkcipozdrav('Petře') // 2. zavolej ji → 'Čau, Petře!'
Továrnu jde napsat i kratčeji, dvěma šipkovými funkcemi za sebou. První šipka bere greeting a vrací druhou šipku, která bere name:
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é?
Normálně platí: proměnné vytvořené uvnitř funkce (její parametry a všechno, co v ní založíš přes let a const) žijí jen během jejího zavolání. Funkce doběhne a proměnné zmizí, jako když po hodině smažeš tabuli.
Jenže funkce, která vznikne uvnitř jiné funkce, si při vzniku nabalí batůžek se všemi proměnnými, které v tom místě vidí. Dokud tahle vnitřní funkce existuje, existuje i batůžek, a proměnné v něm proto nezmizí. Když ji pak zavoláš kdekoli jinde, sáhne si do batůžku. Funkci i s jejím batůžkem se říká closure (uzávěr).
function makeGreeter(greeting) {// greeting je proměnná funkce makeGreeterreturn (name) => `${greeting}, ${name}!`// ↑ tahle šipka vzniká tady, uvnitř makeGreeter, a vidí greeting,// takže si ho přibalí do batůžku}const czech = makeGreeter('Ahoj')// makeGreeter skončila. Bez closure by greeting zmizel, jenže šipka// (teď uložená v czech) ho má v batůžku, takže greeting žije dál.czech('Jano') // šipka sáhne do batůžku: greeting = 'Ahoj' → 'Ahoj, Jano!'
Krok za krokem:
makeGreeter('Ahoj') se zavolá, vznikne proměnná greeting s hodnotou 'Ahoj'.(name) => …. Do batůžku si přibalí proměnné, které v tom místě vidí, tedy i greeting.makeGreeter šipku vrátí a skončí. Šipka se uloží do czech a batůžek jde s ní.czech('Jano') zavolá šipku. Ta má name z argumentu a greeting vytáhne z batůžku.Funkce z batůžku nejen čte, ale může proměnnou v něm i měnit. A když uvnitř jednoho zavolání vznikne funkcí víc, sdílejí jeden společný batůžek. Na tom stojí tahle továrna na počítadla:
function makeCounter() {let count = 0 // proměnná tohohle zavolání makeCounterreturn {increment: () => { count++ }, // obě šipky vznikly ve stejném zavolání,get: () => count, // takže sdílejí jeden batůžek s jedním count}}const a = makeCounter() // 1. zavolání → batůžek A s vlastním count = 0const b = makeCounter() // 2. zavolání → batůžek B s dalším count = 0a.increment() // count v batůžku A: 1a.increment() // count v batůžku A: 2b.increment() // count v batůžku B: 1a.get() // 2 – get čte stejný count, který měnil incrementb.get() // 1 – počítadla se neovlivňujícount // ❌ ReferenceError – zvenku se k count nedostaneš, je „soukromá“
Všimni si tří věcí. increment a get pracují se stejnou proměnnou, proto get vidí, co increment změnil. Každé zavolání makeCounter() vytvoří nový count, proto se a a b neovlivňují. A protože count je jen v batůžku, zvenku ho nikdo nepřepíše: změnit ho jde jen přes increment.
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…
Batůžek si při vzniku funkce neudělá opis hodnot. Nese si samotné proměnné, a hodnotu z nich přečte až ve chvíli, kdy funkci zavoláš. Když se proměnná mezitím změní, funkce uvidí novou hodnotu:
let message = 'Ahoj'const show = () => console.log(message) // show má v batůžku proměnnou messagemessage = 'Nazdar' // změníme proměnnou (show zatím nikdo nevolal)show() // 'Nazdar' – show čte message až teď, v okamžiku zavolání
Funkce se tedy na proměnnou „dívá“, nemá si ji vyfocenou. Proto počítadlo výše funguje: get pokaždé přečte aktuální count.
Každá funkce, kterou napíšeš uvnitř komponenty (obsluha kliknutí, callback pro map, funkce v efektu), je closure. Komponenta je obyčejná funkce, takže všechno, co je v ní, má v batůžku: props, stav i další proměnné.
function Greeting({ name }) {// handleClick vzniká uvnitř komponenty → name má v batůžkuconst handleClick = () => alert(`Ahoj, ${name}!`)return <button onClick={handleClick}>Pozdravit</button>}
Je tu ale háček. Každý render komponenty je nové zavolání funkce, a tedy nový batůžek s novými proměnnými. Obsluha vytvořená v jednom renderu má v batůžku proměnné právě z toho renderu a pozdější rendery je nezmění (stav je v nich const). Tomu se říká snímek (snapshot) a podrobně ho rozebírá lekce JavaScript 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)
Kostry dvou šipkových funkcí zatím vracejí pevnou hodnotu. Přepiš to, co stojí za return, aby počítaly s číslem, které dostanou.
Zadání najdeš v komentářích v kódu níže – na začátku souboru a u míst označených TODO.
Načítám interaktivní část…
Šest malých funkcí. Tři z nich (makeGreeter, makeMultiplier, once) nevracejí hodnotu, ale vytvoří a vrátí novou funkci; další funkci dostávají jako argument nebo vracejí objekt s funkcemi. Procvičíš si parametry, callbacky a closures. Co přesně má která funkce dělat, je v komentářích v kódu.
Střední a těžké výzvy se odemknou, jakmile se přihlásíš. Stejně jako celý kurz jsou zdarma – stačí e-mail, žádné heslo ani platba.
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).