Lekce 0.4 · JavaScript obecně
Pole I: map, filter a spol.
Indexy, map, filter, find, some, every, forEach a řetězení metod.
Načítám lekci…
Lekce 0.4 · JavaScript obecně
Indexy, map, filter, find, some, every, forEach a řetězení metod.
Načítám lekci…
Pole je seřazená řada hodnot – jako vagóny vlaku nebo bedny na pásovém dopravníku. Každá má své pořadové číslo (index) a počítá se od nuly.
Představ si třídírnu ovoce s pásovým dopravníkem. Na pás položíš bednu jablek a podle toho, jaký stroj na konci stojí, dostaneš něco jiného:
map) – každé jablko projde strojem a vyjede oloupané. Kolik jablek vjelo, tolik jich vyjede.filter) – propustí jen některá jablka. Jablka se nemění, jen jich je míň.find) – zastaví pás u prvního jablka, které hledáš, a podá ti ho.some/every) – řekne jen „ano“ nebo „ne“: je tam aspoň jedno shnilé? Jsou všechna zralá?Všechny tyhle stroje fungují stejně: dostanou funkci (callback) a zavolají ji pro každou položku. Ty jen napíšeš, co se má stát s jednou položkou. V Reactu je to denní chleba – každý seznam na stránce vzniká přes map, každé vyhledávání přes filter.
const fruits = ['jablko', 'hruška', 'švestka']const empty = []const mixed = [1, 'text', true, { a: 1 }, [1, 2]] // pole může obsahovat cokolifruits.length // 3fruits[0] // 'jablko' – první položka má index 0fruits[2] // 'švestka' – poslední má index length - 1fruits[3] // undefined – mimo rozsah není chybafruits.at(-1) // 'švestka' – záporný index počítá od koncefruits.at(-2) // 'hruška'fruits.length === 0 // je pole prázdné?Array.isArray(fruits) // true – tak se pozná pole (typeof vrací 'object')
Nejčastější podoba dat v reálné aplikaci – a v Reactu taky:
const users = [{ id: 1, name: 'Jana', city: 'Praha' },{ id: 2, name: 'Petr', city: 'Brno' },]users[1].name // 'Petr'users[0].city // 'Praha'users.at(-1).id // 2
Array.from({ length: 3 }, (_, i) => i + 1) // [1, 2, 3] – pole o dané délceArray.from('ahoj') // ['a', 'h', 'o', 'j']'a,b,c'.split(',') // ['a', 'b', 'c'][...'ahoj'] // ['a', 'h', 'o', 'j']
const fruits = ['jablko', 'hruška', 'švestka']fruits.includes('hruška') // truefruits.includes('banán') // falsefruits.indexOf('švestka') // 2fruits.indexOf('banán') // -1 – „nenašel“
const fruits = ['jablko', 'hruška', 'švestka']// Klasický for – když potřebuješ index nebo přeskakovatfor (let i = 0; i < fruits.length; i++) {console.log(i, fruits[i])}// for…of – nejčitelnější, když chceš jen projít položkyfor (const fruit of fruits) {console.log(fruit)}// for…of s indexemfor (const [i, fruit] of fruits.entries()) {console.log(i, fruit)}// forEach – zavolá funkci pro každou položkufruits.forEach((fruit, i) => console.log(i, fruit))
| for / for…of | forEach | map | |
|---|---|---|---|
| Vrací | nic (je to příkaz) | undefined | nové pole |
| Jde přerušit (break)? | ano | ne | ne |
| Kdy | smyčka s break, await uvnitř | vedlejší efekt pro každou položku (log, uložení) | když chceš z pole udělat jiné pole |
map zavolá tvoji funkci pro každou položku a z jejích návratových hodnot složí nové pole stejné délky. Původní pole se nemění.
const numbers = [1, 2, 3, 4]numbers.map((n) => n * 2) // [2, 4, 6, 8]numbers.map((n) => n * n) // [1, 4, 9, 16]numbers.map((n) => `#${n}`) // ['#1', '#2', '#3', '#4']numbers.map((n) => n > 2) // [false, false, true, true]numbers.map(String) // ['1', '2', '3', '4'] – hotová funkce jako callbacknumbers // [1, 2, 3, 4] – beze změny
const users = [{ id: 1, first: 'Jana', last: 'Nováková', age: 31 },{ id: 2, first: 'Petr', last: 'Svoboda', age: 17 },]// Vytáhnout jednu vlastnostusers.map((u) => u.id) // [1, 2]// Spočítat něco novéhousers.map((u) => `${u.first} ${u.last}`) // ['Jana Nováková', 'Petr Svoboda']// Destrukturace přímo v parametruusers.map(({ first, age }) => `${first} (${age})`) // ['Jana (31)', 'Petr (17)']// Nový objekt s vybranými vlastnostmi – POZOR na kulaté závorkyusers.map((u) => ({ id: u.id, name: u.first })) // [{ id: 1, name: 'Jana' }, …]// Kopie s přidanou vlastností (původní objekty zůstanou)users.map((u) => ({ ...u, isAdult: u.age >= 18 }))
const fruits = ['jablko', 'hruška', 'švestka']fruits.map((fruit, i) => `${i + 1}. ${fruit}`) // ['1. jablko', '2. hruška', '3. švestka']fruits.map((_, i) => i) // [0, 1, 2] – _ = „tenhle parametr nepotřebuju“
filter zavolá funkci pro každou položku. Když funkce vrátí true (nebo cokoli pravdivého), položka projde do nového pole – celá a nezměněná.
const numbers = [5, 12, 8, 130, 44]numbers.filter((n) => n > 10) // [12, 130, 44]numbers.filter((n) => n % 2 === 0) // [12, 8, 130, 44] – sudánumbers.filter((n) => n > 1000) // [] – žádná shoda = prázdné pole, ne undefined
const products = [{ id: 1, name: 'Jablko', price: 12, inStock: true, category: 'ovoce' },{ id: 2, name: 'Hruška', price: 15, inStock: false, category: 'ovoce' },{ id: 3, name: 'Mrkev', price: 6, inStock: true, category: 'zelenina' },]products.filter((p) => p.inStock) // Jablko, Mrkevproducts.filter((p) => !p.inStock) // Hruškaproducts.filter((p) => p.category === 'ovoce') // Jablko, Hruškaproducts.filter((p) => p.price >= 10 && p.inStock) // Jablko// Odebrat položku podle id – „nech všechno KROMĚ“products.filter((p) => p.id !== 2) // Jablko, Mrkev// Vyhledáváníconst query = 'ja'products.filter((p) => p.name.toLowerCase().includes(query.toLowerCase())) // Jablko// Vyhodit prázdné hodnoty['a', '', null, 'b', undefined].filter(Boolean) // ['a', 'b']
const users = [{ id: 1, name: 'Jana', city: 'Praha' },{ id: 2, name: 'Petr', city: 'Brno' },{ id: 3, name: 'Eva', city: 'Brno' },]users.find((u) => u.id === 2) // { id: 2, name: 'Petr', city: 'Brno' } – objekt, ne poleusers.find((u) => u.city === 'Brno') // Petr – jen PRVNÍ shodausers.find((u) => u.id === 99) // undefinedusers.findIndex((u) => u.id === 3) // 2 – poziceusers.findIndex((u) => u.id === 99) // -1users.findLast((u) => u.city === 'Brno') // Eva – hledá od konce
| find | filter | |
|---|---|---|
| Vrací | jednu položku (nebo undefined) | vždy pole (i prázdné) |
| Zastaví se | u první shody | projde všechno |
| Typicky | detail podle id | seznam podle podmínky |
const products = [{ name: 'Jablko', price: 12, inStock: true },{ name: 'Hruška', price: 15, inStock: false },]products.some((p) => !p.inStock) // true – je aspoň jeden vyprodaný?products.some((p) => p.price > 100) // falseproducts.every((p) => p.price < 20) // true – jsou všechny levnější než 20?products.every((p) => p.inStock) // false// Typické použitíconst canCheckout = cart.every((item) => item.inStock)const hasErrors = fields.some((f) => f.error)
Protože map i filter vracejí pole, můžeš na výsledek rovnou zavolat další metodu. Čti to shora dolů jako postup v receptu:
const result = products.filter((p) => p.inStock) // 1. jen skladem.filter((p) => p.price < 20) // 2. jen levné.map((p) => p.name.toUpperCase()) // 3. jen jména velkými písmeny// Totéž s mezikroky – někdy čitelnějšíconst inStock = products.filter((p) => p.inStock)const cheap = inStock.filter((p) => p.price < 20)const names = cheap.map((p) => p.name.toUpperCase())
Co vidíš: Šest produktů (dva vyprodané), řada tlačítek s metodami a pod nimi kód, vysvětlení a výsledek. Zvýrazněné produkty jsou ty, které metoda „vzala“. reduce a toSorted jsou ochutnávka příští lekce.
Vyzkoušej:
Co z toho plyne: Než sáhneš po metodě, zeptej se: chci zpátky pole stejné délky (map), kratší pole (filter), jednu položku (find), nebo ano/ne (some/every)?
Načítám interaktivní část…
| Chci… | Metoda | Vrací |
|---|---|---|
| z každé položky udělat něco jiného | map | nové pole stejné délky |
| jen některé položky | filter | nové pole (kratší nebo stejné) |
| první položku, která splňuje podmínku | find | položku, nebo undefined |
| pozici první takové položky | findIndex | číslo, nebo -1 |
| vědět, jestli aspoň jedna splňuje | some | true / false |
| vědět, jestli všechny splňují | every | true / false |
| vědět, jestli pole obsahuje hodnotu | includes | true / false |
| pro každou položku něco udělat (bez výsledku) | forEach | undefined |
Jedno volání map a jedno volání filter – nic víc.
VÝZVA: Dvojnásobky a kladná čísla 1. doubleAll([1, 2, 3]) má vrátit [2, 4, 6]. Použij map. 2. onlyPositive([-2, 5, 0, 3]) má vrátit [5, 3]. Použij filter (kladné = větší než 0).
Načítám interaktivní část…
Osm dotazů nad seznamem úkolů. Každý jde napsat jedním výrazem – vyber správnou metodu (nebo jejich řetěz). Testy se spustí samy a ukážou, které funkce už fungují.
VÝZVA: Dotazy nad seznamem úkolů (map, filter, find, some, every, řetězení) Napiš těla osmi funkcí – každou jedním výrazem s metodou pole. Testy pod nimi se spustí samy a ukážou ✅/❌. Data úkolů najdeš v sekci Testy (pole `tasks`). 1. titles(tasks) – pole názvů úkolů (map). 2. openTasks(tasks) – jen nehotové úkoly, done === false (filter). 3. findById(tasks, id) – úkol s daným id, nebo undefined (find). 4. hasUrgent(tasks) – je mezi úkoly aspoň jeden s priority 'high'? (some) 5. allAssigned(tasks) – mají všechny úkoly vyplněné assignee (není null)? (every) 6. search(tasks, query) – úkoly, jejichž název obsahuje hledaný text bez ohledu na velikost písmen a mezery na krajích dotazu (filter + toLowerCase + trim + includes). 7. openTitlesFor(tasks, person) – názvy NEhotových úkolů daného člověka (filter → map). 8. numbered(tasks) – ['1. Nakoupit', '2. Uklidit', …] (map s druhým parametrem index).
Načítám interaktivní část…
arr.at(-1), mimo rozsah je undefined.includes/indexOf jen pro jednoduché hodnoty; objekty hledej přes some/find podle vlastnosti.return a ({ … }).!==.undefined – hlídej ji přes ?..true.Checks, deepFreeze, equal, Check)Checks, deepFreeze, equal, 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>)}/** Freezes an object and all nested objects and arrays – any attempt to change them throws. */export function deepFreeze<T>(value: T): T {if (value && typeof value === 'object') {Object.values(value).forEach(deepFreeze)Object.freeze(value)}return value}/** Compares two values by content (arrays and objects item by item), not by reference. */export function equal(a: unknown, b: unknown): boolean {return JSON.stringify(a) === JSON.stringify(b)}
Checks, deepFreeze, equal, Check)Checks, deepFreeze, equal, 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>)}/** Freezes an object and all nested objects and arrays – any attempt to change them throws. */export function deepFreeze<T>(value: T): T {if (value && typeof value === 'object') {Object.values(value).forEach(deepFreeze)Object.freeze(value)}return value}/** Compares two values by content (arrays and objects item by item), not by reference. */export function equal(a: unknown, b: unknown): boolean {return JSON.stringify(a) === JSON.stringify(b)}