Lekce 0.3 · JavaScript obecně
Objekty
Tečka vs. hranaté závorky, destrukturace, spread, Object.entries a dynamické klíče.
Načítám lekci…
Lekce 0.3 · JavaScript obecně
Tečka vs. hranaté závorky, destrukturace, spread, Object.entries a dynamické klíče.
Načítám lekci…
Představ si kartotéku v knihovně. Každý lístek popisuje jednu knihu a má kolonky s nadpisy: název, autor, rok. Objekt v JavaScriptu je přesně takový lístek – sada pojmenovaných kolonek (vlastností), každá s nějakou hodnotou.
const book = {title: 'Malý princ', // klíč: hodnotaauthor: 'Saint-Exupéry',year: 1943,available: true,}
Objekty jsou všude: uživatel, produkt v košíku, nastavení aplikace, odpověď ze serveru. V Reactu jsou objekt i props, které komponenta dostane. Kdo umí s objekty šikovně zacházet, píše kratší a čitelnější kód.
const user = { name: 'Jana', city: 'Praha', 'favorite color': 'modrá' }user.name // 'Jana' – tečka: jméno znáš při psaní kóduuser['name'] // 'Jana' – závorky: totéžuser['favorite color'] // 'modrá' – klíč s mezerou jde přečíst jen závorkamiconst key = 'city'user[key] // 'Praha' – jméno vlastnosti je v PROMĚNNÉuser.key // undefined – hledá vlastnost, která se doslova jmenuje „key“
| Zápis | Kdy |
|---|---|
obj.name | Jméno vlastnosti znáš už při psaní kódu – většina případů. |
obj[key] | Jméno je v proměnné nebo se počítá za běhu (formulář s mnoha poli, slovník, smyčka). |
const order = {id: 7,customer: { name: 'Petr', address: { city: 'Brno' } },}order.customer.address.city // 'Brno' – vnořené objekty čteš tečkou za tečkouorder.note // undefined – neexistující vlastnost není chybaorder.delivery.date // 💥 TypeError: Cannot read properties of undefinedorder.delivery?.date // undefined – ?. se zastaví, když něco chybí (lekce 0.6)
const user = { name: 'Jana' }user.age = 31 // přidá novou vlastnostuser.name = 'Jana N.' // změní existujícíuser['city'] = 'Praha' // totéž se závorkamidelete user.age // smaže vlastnostuser // { name: 'Jana N.', city: 'Praha' }
const name = 'Jana'const age = 31const user = { name: name, age: age } // postaruconst user = { name, age } // zkratka: proměnná se jmenuje stejně jako klíč// Metoda – funkce uložená ve vlastnosticonst counter = {count: 0,increment() { // zkratka za increment: function () { … }this.count++},}
Když jméno vlastnosti znáš až za běhu (je v proměnné), dej ho při vytváření objektu do hranatých závorek:
const field = 'email'const value = 'jana@example.cz'const update = { [field]: value } // { email: 'jana@example.cz' }const wrong = { field: value } // { field: 'jana@example.cz' } – doslova „field“// Typické použití: jedna funkce pro všechna pole formulářefunction updateForm(form, field, value) {return { ...form, [field]: value }}updateForm({ name: '', email: '' }, 'name', 'Jana') // { name: 'Jana', email: '' }const prefix = 'color'const theme = { [`${prefix}Primary`]: 'blue' } // { colorPrimary: 'blue' }
Destrukturace je vybalení krabice: místo „vezmi krabici a z ní věc s nálepkou name“ řekneš rovnou „chci věci name a city“.
const user = { name: 'Jana', city: 'Praha', age: 31 }const { name, city } = user // dvě proměnné najednouconst { name: userName } = user // přejmenování: proměnná se bude jmenovat userNameconst { role = 'host' } = user // výchozí hodnota, když vlastnost chybí (nebo je undefined)const { name: n, role: r = 'host' } = user // přejmenování i výchozí hodnotaconst { age, ...rest } = user // rest = { name: 'Jana', city: 'Praha' }
const order = { id: 7, customer: { name: 'Petr', address: { city: 'Brno' } } }const { customer: { name, address: { city } } } = ordername // 'Petr'city // 'Brno'// customer ani address jako proměnné nevznikly – jen jsme jimi „prošli“
// Funkce dostane objekt a rovnou si z něj vybalí, co potřebujefunction formatUser({ name, city = 'neznámé' }) {return `${name} (${city})`}formatUser({ name: 'Jana', city: 'Praha', age: 31 }) // 'Jana (Praha)'// Přesně takhle vypadá komponenta v Reactu:function UserCard({ name, city }) { … }// A v callbacku – každá položka pole je objektusers.map(({ name }) => name)
U pole se nevybaluje podle jména, ale podle pořadí:
const [first, second] = ['jablko', 'hruška', 'švestka'] // 'jablko', 'hruška'const [, , third] = ['jablko', 'hruška', 'švestka'] // přeskočit: 'švestka'const [head, ...tail] = [1, 2, 3, 4] // 1 a [2, 3, 4]let a = 1, b = 2;[a, b] = [b, a] // prohození: a = 2, b = 1 (středník na začátku je tu nutný)const [count, setCount] = useState(0) // ← v Reactu uvidíš pořád
Tři tečky před objektem znamenají „vysyp sem všechny jeho vlastnosti“.
const user = { name: 'Jana', city: 'Praha' }const copy = { ...user } // mělká kopie: nový objekt, stejné vlastnosticopy === user // false – jsou to dva různé objektyconst moved = { ...user, city: 'Brno' } // kopie s přepsanou vlastnostíconst withAge = { ...user, age: 31 } // kopie s novou vlastnostíconst defaults = { theme: 'light', lang: 'cs', fontSize: 16 }const saved = { theme: 'dark' }const settings = { ...defaults, ...saved } // { theme: 'dark', lang: 'cs', fontSize: 16 }
const isAdmin = trueconst user = {name: 'Jana',...(isAdmin ? { role: 'admin' } : {}), // vlastnost role přidá jen pro admina}
const prices = { jablko: 12, hruška: 15, švestka: 8 }Object.keys(prices) // ['jablko', 'hruška', 'švestka']Object.values(prices) // [12, 15, 8]Object.entries(prices) // [['jablko', 12], ['hruška', 15], ['švestka', 8]]for (const [fruit, price] of Object.entries(prices)) {console.log(`${fruit} stojí ${price} Kč`)}Object.keys(prices).length // 3 – počet vlastnostíObject.keys({}).length === 0 // true – tak se pozná prázdný objekt// Z dvojic zpátky na objektObject.fromEntries([['a', 1], ['b', 2]]) // { a: 1, b: 2 }
Kombinace entries → úprava → fromEntries je běžný způsob, jak „přepočítat“ všechny hodnoty objektu. Metody polí jako map a filter přijdou v příští lekci, pak to půjde napsat na jeden řádek.
// Zdražit všechno o 10 %const result = {}for (const [fruit, price] of Object.entries(prices)) {result[fruit] = Math.round(price * 1.1)}// result = { jablko: 13, hruška: 17, švestka: 9 }'jablko' in prices // true – má objekt tuhle vlastnost?Object.hasOwn(prices, 'banán') // false
Co vidíš: Objekt user a tlačítka s operacemi. Pro každou vidíš kód, vysvětlení a výsledek.
Vyzkoušej:
city se přejmenovalo na town a chybějící email dostal výchozí hodnotu.Co z toho plyne: Objekt jde číst tečkou i závorkami, vybalit destrukturací, kopírovat spreadem a převést na pole přes Object.entries.
Načítám interaktivní část…
Objekt nemusí popisovat jednu věc. Často slouží jako slovník (překladová tabulka): klíč → hodnota. Nahradí tím dlouhé řady if/else:
// ❌ Řada podmínekfunction statusLabel(status) {if (status === 'new') return 'Nová'if (status === 'paid') return 'Zaplacená'if (status === 'shipped') return 'Odeslaná'return 'Neznámý stav'}// ✅ Slovníkconst STATUS_LABELS = {new: 'Nová',paid: 'Zaplacená',shipped: 'Odeslaná',}const statusLabel = (status) => STATUS_LABELS[status] ?? 'Neznámý stav'
// Počítadlo výskytůconst votes = ['ano', 'ne', 'ano', 'ano']const counts = {}for (const v of votes) {counts[v] = (counts[v] ?? 0) + 1}counts // { ano: 3, ne: 1 }// Rychlé hledání podle id místo procházení celého poleconst users = [{ id: 7, name: 'Jana' }, { id: 9, name: 'Petr' }]const usersById = {}for (const u of users) {usersById[u.id] = u}usersById[9].name // 'Petr'
Přečti dvě vlastnosti objektu a vyrob jeho kopii s jednou vlastností navíc.
VÝZVA: Vizitka
1. fullName(person) má vrátit jméno a příjmení s mezerou: 'Jana Nováková'.
Vlastnosti čti tečkou: person.first, person.last
2. withCity(person, 'Brno') má vrátit NOVÝ objekt s přidaným městem: { ...person, city }Načítám interaktivní část…
Šest malých funkcí na práci s objekty: dynamické klíče, odebrání vlastnosti, výběr vlastností, počítání a přepočet hodnot. Vstupy jsou zmrazené, takže je nemůžeš omylem změnit.
Střední a těžké výzvy jsou bonus pro přihlášené. Registrace je zdarma – stačí e-mail, žádné heslo.
undefined, ale čtení z undefined spadne – pomůže ?..{ name } a vypočítaný klíč { [field]: value }.name: n), dát výchozí hodnotu (= 'host') i posbírat zbytek (...rest).Object.keys / values / entries převedou objekt na pole, fromEntries zpátky.Checks, deepFreeze, Check)Checks, deepFreeze, 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}