6

Funkce a obory platnosti

Procvičíme si psaní vlastních funkcí a ukážeme si jak pracovat s obory platnosti proměnných.

Do této chvíle jsme z jazyka JavaScript již poznali mnoho věcí. Věcí, které často potřebují čas na strávení a zažití aby se v hlavě dobře usadily na ta správná místa. Pokud se něco nového a náročného snažíme naučit příliš rychle, snadno se stane, že nám v hlavách nové pojmy lítají jak splašené a není jasné, co souvisí s čím a co kam patří. V této lekci tedy vrhneme více světla na věci, které jste už v minulých lekcích použili, ale možná ještě nebyl čas se nad nimi pořádně zamyslet.

Hodnoty null a undefined

Občas se nám stane, že si potřebujeme nějakou proměnnou připravit, ale zatím ještě nevíme, jaká v ní bude hodnota. Chceme tedy, aby na začátku v proměnné byla nějaká neutrální hodnota, která jakoby nic neznamená. To můžeme zařídit pomocí speciální hodnoty null. Můžeme si představit, že hodnota null je jakási prázdná výplň, taková vata, která zabírá místo tam, kde zatím nic skutečného není. Jde zároveň o nový typ hodnoty vedle čísel, řetězců, objektů, funkcí apod.

'use strict';

const password = prompt('Zadejte heslo: ');
let message = null;

if (password === 'swordfish') {
  message = 'Access granted';
} else {
  message = 'Access denied';
}

const msgElm = document.querySelector('#msg');
msgElm.textContent = message;

Explicitnímu ukládání hodnoty null do proměnných jako výše, bychom se měli spíše vyhýbat. Uvedený program se dá bez problému přepsat bez použití null.

let message = 'Access denied';

if (password === 'swordfish') {
  message = 'Access granted';
}

const msgElm = document.querySelector('#msg');
msgElm.textContent = message;

Často se však stane, že hodnotu null vrací nějaké funkce v situaci, kdy se něco nepovedlo. Velmi častý případ je to právě u funkce document.querySelector, která vrací null, pokud se jí na stránce nezdaří najít element podle zadaného selektoru.

Pojďme zkusit omylem vybrat element pro naší zprávu pomocí CSS třídy, která však v HTML vůbec není.

> const msgElm = document.querySelector('.msg')
> msgElm
null

Vidíme, že v proměnné msgElm máme místo očekávaného elementu uloženo null. Z toho si domyslíme, že metoda querySelector kýžený element nenašla a můžeme začít zkoumat, kde jsme v programu udělali chybu.

Pokud to v programu potřebujeme, můžeme proměnnou na hodnotu null otestovat pomocí jednoduché podmínky.

if (msgElm === null) {
  console.log('Element nenalezen');
}

Hodnota undefined

Kromě celkem užitečné hodnoty null JavaScript také obsahuje zákeřnou hodnotu undefined. Pokud null je jakási neutrální výplň, hodnota undefined v podstatě znamená “prázdno”. Pokud bychom přirovnali proměnné k šuplíkům, mohli bychom si představovat, že hodnota null znamená šuplík vyplněný vatou nebo molitanem. Hodnota undefined by pak znamenala, že šuplík je úplně prázdný.

Hodnotu undefined potkáme v mnoha situacích, ale nejčastěji ve chvíli, kdy se snažíme u nějakého objektu přistoupit k vlastnosi, která neexistuje. Je například velmi snadné udělat překlep v anglickém slově length.

> const name = 'martin'
> name.lenght
undefined

Všimněte si, že JavaScript runtime vrací undefined také jako výsledek vytvoření proměnné. Kód uvedený výše tak ve skutečnosti vypadá v konzoli takto.

> const name = 'martin'
undefined
> name.lenght
undefined

Hodnotu undefined najdeme také v proměnných, do kterých nepřiřadíme žádnout hodnotu. Toto je však možné provést pouze s proměnnými vytvořenými pomocí let.

> let name
undefined
> name
undefined

Podobně jako u hodnoty null můžeme přítomnost hodnoty undefined ověřit podmínkou.

if (name === undefined) {
  console.log('Něco se pokazilo');
}

Hodnota undefined nám v budoucní způsobí ještě hodně nepříjemností, je tedy dobré se již teď obrnit trpělivostí.

Speciální druhy funkcí

Většina práce v JavaScriptu se točí kolem vytváření a volání funkcí. Během programování tak často budeme narážet na různé jejich podoby a příchuťe. Je proto dobré se už od začátku začít učit, k čemu tyto různé podoby slouží.

Podle druhu vykonávané práce můžeme funkce rozdělit v podstatě na dvě skupiny:

  1. funkce, které vyrábějí nějaký výsledek (hodnotu),
  2. funkce, které nic nevyrábějí a pouze vykonají nějaký kus práce.

Funkce vyrábějící nějakou hodnotu tuto hodnotu vrací pomocí return. Naopak funkce, které pouze vykonávají nějakou práci žádnou hodnotu nevyrábějí a slovíčko return tedy nepoužívají.

Funkce bez návratové hodnoty

Představte si například funkci, jejíž úkolem je vypsat do stránky nějakou souhrnou informaci, například shrnutí objednávky v e-shopu.

const showOrderSummary = (summary) => {
  const summaryElm = document.querySelector('#summary');
  summaryElm.innerHTML = `
    <div class="summary__count">Celkem položek: ${summary.count}</div>
    <div class="summary__sum">Částka: ${summary.sum}</div>
  `;
}

K tomu, aby funkce splnila svůj úkol stačí, že do stránky vloží patřičný kus HTML. Už nemusí vracet žádnou hodnotu. JavaScript se však snaží být konzistentní a i takováto funkce tajně vrací hodnotu. Pokud sami nenapíšeme slovíčko return, JavaScript si jej sám doplní na konec funkce.

const showOrderSummary = (summary) => {
  const summaryElm = document.querySelector('#summary');
  summaryElm.innerHTML = `
    <div class="summary__count">Celkem položek: ${summary.count}</div>
    <div class="summary__sum">Částka: ${summary.sum}</div>
  `;

  return undefined;
}

V JavaScriptu tedy každá funkce něco vrací. Pokud my sami z naší funkce nevrátíme žádnou hodnotu, JavaScript automaticky vrátí undefined. Proto můžeme klidně psát

const result = showOrderSummary({ count: 5, sum: 5270 });

V proměnné result pak po tomto příkazu bude uložena hodnota undefined. Vzhledem k tomu, že funkce showOrderSummary vrací undefined vždy, ukládat si její výsledek je zbytečné. Takovou funkci prostě zavoláme takto.

showOrderSummary({ count: 5, sum: 5270 });

O její návratovou hodnotu se nestaráme, protože nám k ničemu není. Z této části si však můžeme odnést důležité poučení:

Každá funkce vrací nějakou hodnotu, i když uvnitř nepoužijeme return.

Funkce bez parametrů

V praxi běžně narazíme také na funkce, které žádné parametry nemají. V takovém případě na místo parametrů píšeme prostě prázdné závorky. Příkladem může být následující funkce, která pro nás hodí kostkou, tedy vygeneruje náhodné celé číslo mezi 1 a 6.

const roll = () => {
  return Math.floor(Math.random() * 6) + 1;
};

Tato funkce ke své činnosti žádné hodnoty z venku napotřebuje, proto je bez parametrů. Další příklad může být funkce, který obarví nadpis stránky na červeno.

const colorHeadingRed = () => {
  const headingElm = document.querySelector('h1');
  headingElm.style.color = 'red';
};

Tato funkce parametry nemá, protože ke své činnosti opět nepotřebuje žádné informace z venku. Mohli bychom však také chtít funkci, která obarví nadpis námi zvolenou barvou. V takovém případě funkci přidáme jeden parametr.

const colorHeading = (colorName) => {
  const headingElm = document.querySelector('h1');
  headingElm.style.color = colorName;
};

Cvičení: Procvičování funkcí

1

Pozdravy

pohodička

Představte si, že tvoříte aplikaci na odesílání e-mailů. Každý e-mail je třeba zakončit zdvořilým pozdravem.

  1. Napište funkci bez parametrů s názvem goodbye. Funkce do konzole vypíše rozloučení. Příklad použití:
    > goodbye()
    Na shledanou
    
  2. Končit e-mail slovy „na shledanou“ je nezdvořilé. Přidejte proto do funkce goodbye parametr představující jméno pisatele e-mailu. Funkce do konzole vypíše koncový pozdrav i se jménem. Příklad použití:
    > goodbye('Pavel Ovesný')
    S pozdravem Pavel Ovesný
    
  3. Upravte funkci goodbye tak, aby místo vypisování do konzole vrátila pozdrav jako řetězec. Příklad:
    > goodbye('Pavel Ovesný')
    'S pozdravem Pavel Ovesný'
    
2

E-mail

to dáš

Stáhněte si základ stránky, která zobrazuje jeden e-mail.

  1. Napište funkci fillSubject s jedním parametrem subject. Tato funkce ze stránky vybere DOM element představující předmět e-mailu a nastaví jeho obsah na řetězec, který přišel v parametru. Zavolejte funkci z konzole a vykoušejte si nastavit předmět e-mailu na různé řetězce.
  2. Napište funkci fillBody s jedním parametrem body, která ze stránky vybere DOM element představující tělo e-mailu a nastaví jeho obsah dle hodnoty parametru. Funkci vyzkoušejte v konzoli.
  3. Zkopírujte si do programu funkci goodbye z předchozího cvičení. Do funkce fillBody přidejte další parametr s názvem name. Tento parametr bude představovat jméno odesílatele. Funkce vyplní tělo emailu a na konec přidá pozdrav, který vyrobí pomocí volání funkce goodbye.
3

Převod měny

to dáš

Napište funkci convertToCZK, která převede částku zadanou v cízí měně na české koruny. Funkce bude podporovat následující měny a kurzy.

Měna Kód Kurz
Euro EUR 27.015
Britská libra GBP 29.615
Americký dolar USD 23.197

Výslednou částku zakrouhlete na celé koruny. Příklad použití:

> convertToCZK(25, 'EUR')
675

Pokud funkce jako parametr dostane neznámý kód měny, vrátí jako výsledek null. Otestujte funkci v konzoli.

Obor platnosti proměnných

Mějme následující podmínku, která kontroluje věk uživatele a vypisuje neurvalé hlášky.

if (age < 18) {
  const remains = 18 - age;

  if (remains <= 2) {
    console.log('Už to máš za pár');
  } else if (remains <= 5) {
    console.log(`Ještě si počkáš ${remains} let`);
  } else {
    console.log('Utíkej za mamkou');
  }
} else {
  console.log('Vítej mezi dospěláky');
}

Zatím nebudeme řešit odkud se vzala proměnná age. Především si všimneme, že celý program obsahuje dohromady pět různých bloků kódu oddělených složenými závorkami. Pokud uvnitř nějakého bloku vytvoříme proměnnou, například remains, tato proměnná je “vidět” pouze uvnitř tohoto bloku. Tento blok se stává jejím oborem platnosti scope . Jakmile její blok kódu skončí, proměnná remains zanikne a již s ní není možné pracovat.

Pokud se proměnnou pokusíme použít mimo její obor platnosti, JavaScript runtime se bude tvářit jako kdyby tuto proměnnou nikdy neviděl.

if (age < 18) {
  const remains = 18 - age;

  if (remains >= 2) {
    console.log('Už to máš za pár');
  } else if (remains >= 5) {
    console.log(`Ještě si počkáš ${remains} let`);
  } else {
    console.log('Utíkej za mamkou');
  }
} else {
  console.log(remains); // Zde vznikne chyba
  console.log('Vítej mezi dospěláky');
}

console.log(remains); // Zde vznikne chyba

Naopak všechny bloky zanořené uvnitř bloku, ve kterém byla proměnná vytvořene, k této proměnné přistupovat mohou. To můžeme v našem kódu vidět v bloku else if, kde proměnnou remains normálně používáme, přestože je vytvořena o blok výše.

Pokud tedy JavaScript runtime narazí uvnitř nějakého bloku na něco, co vypadá jako jméno proměnné, zkusí tuto proměnnou najít uvnitř tohoto bloku. Pokud se mu to nezdaří, podívá se do bloku a patro výš. Takto postupně prochází všechny nadřezené bloky, dokud nenarazí na nejvyšší patro – takzvaný globální obor platnosti global scope .

Globální obor platnosti

Každý JavaScriptový program si můžeme představeit jako jeden velký blok kódu, který v sobě obsahuje všechny příkazy. Takto vznikne globální obor platnosti, ve kterém JavaScript runtime nakonec hledá všechny proměnné, které nanašel nikde jinde. Ukažme si náš program kontrolující věk v celé své kráse.

const age = Number(prompt('Zadej svůj věk:'));

if (age < 18) {
  const remains = 18 - age;

  if (remains >= 2) {
    console.log('Už to máš za pár');
  } else if (remains >= 5) {
    console.log(age); // V pořádku
    console.log(`Ještě si počkáš ${remains} let`);
  } else {
    console.log('Utíkej za mamkou');
  }
} else {
  console.log(age); // V pořádku
  console.log('Vítej mezi dospěláky');
}

console.log(age); // V pořádku

V tomto programu vidíme, že proměnná age je vytvořená v globálním oboru platnosti. Takové proměnné říkáme prostě globální. Globální proměnné jsou vidět v celém programu a můžeme je tedy použít kdekoliv. Pokud proměnná není globální a je tedy vytvořena uvnitř nějakého bloku, říkáme o ni, že je lokální local .

Obory platnosti nám pomáhají rodělit náš kód na menší samostatné celky, které se navzájem neovlivňují. Můžete tak bez problému mít ve dvou blocích stejně pojmenovavnou lokální proměnnou a význam bude zcela jasný.

const age = Number(prompt('Zadej svůj věk:'));

if (age < 18) {
  const message = 'Utíkej za mamkou';
  console.log(message);
} else {
  const message = 'Vítej mezi dospěláky';
  console.log(message);
}

V tom příkladu máme dvě lokální proměnné message, které náhodou mají stejné jméno, jinak však spolu nemají nic společného.

Zastiňování proměnných

Uvažujíc nad příkladem výše vás možná napadne, co by se stalo, kdybychom proměnné message vytvořili takto.

const age = Number(prompt('Zadej svůj věk:'));
const message = 'Utíkej za mamkou';

if (age < 18) {
  console.log(message);
} else {
  const message = 'Vítej mezi dospěláky';
  console.log(message);
}

Pravidlo při hledání proměnných říká, že se použije ta deklarace, na kterou runtime při procházení nadřazených bloků narazí nejdříve. Díky tomu, že se prohledává vždy od nejnižšího patra k nejvyššímu, v bloku if narazíme nejdřív na globální proměnnou message. Naopak v bloku else dříve najdeme lokální proměnnou. Tomuto principu se říká zastínění shadowing . Proměnná, která je z hlediska hierarchie bloků níže, takzvaně zastíní stejně pojmenovou proměnnou, která se nachází výše.

V praxi je nejlepší, když má náš program tak dobře pojmenované proměnné, že se nevzájem nazastiňují. Zjednodušujeme tak práci všem čtenářům, kteří tak mají o starost méně při louskání našeho kódu. Rozhodně je ale dobré vědět, že zastínění může nastat a JavaScript runtime se s ním snadno vypořádá.

Obory platnosti a funkce

Jak po předchozích lekcích už všichni víme, bloky kódu se používají také k vytváření funkci. Zde do oborů platnosti vstupuje další hráč, a to jsou parametry funkce. Ty se z hlediska hierarchie nacházejí jakoby na rozhraní mezi blokem funkce a jeho nadřazeným blokem. Prohlédněte si porozně následující kód.

'use strict';

const message = 'Vítej ve světě slasti';

const checkAge = (age, message) => {
  if (age < 18) {
    return message;
  } else {
    const message = 'Vítej mezi dospěláky';
    return message;
  }
};

Vytváříme zde funkci checkAge, která má dva parametry age a message. Uvnitř této funkce parametr message zastíní globální proměnnou message. V bloku else je však tento parametr dále zastíněn lokální proměnnou message. Zkuste si rozmyslet, co pak bude výsledkem těchto volání.

> checkAge(15, 'Utři si sopel')
?
> checkAge(21, 'Oh yeah!')
?

Je dobré připomenout, že program výše je napsán obzvlášť zlovolně je zde především ze vzdělávacích důvdodů. Pokud takový kód někady napíšete v praxi, dostanete od vašich kolegů nejspíš pořádně za uši. Nikdo nechce číst kód, nad kterým musí zbytečně hodinu přemýšlet.

Cvičení: Porozumění kódu

4

Porozumění kódu

to dáš

Přečtěte si následující úryvky kódu a u každého řekněte, co program vypíše do konzole aniž abyste program spouštěli.

Úryvek 1:

const name = 'Mississippi';

if (name.length > 5) {
  const name = 'Missi';
  console.log(name);
}

console.log(name);

Úryvek 2:

const name = 'Franta';

const greet = (name) => {
  const name = 'Pepa';
  console.log(name);
  return 'Kuba';
};

console.log(greet('Jožin'));

Úryvek 3:

const age = 25;

if (age > 21) {
  const price = 100;
} else if (age > 15) {
  const price = 50;
} else {
  const price = 0;
}

console.log(price);

Doporučené úložky na doma

5

Výplata

to dáš

Vytvořte funkci salary se třemi parametry

  • wage - hrubá mzda v korunách za hodinu
  • hours - kolik hodin denně průměrně precujete
  • days - kolik dní v měsící průměrně pracujete

Funkce spočítá vaši hrubou měsíční mzdu v celých korunách.

Dále vytvořte funkci taxed, která na vstupu obdrží částku a procento zdanění, a vrátí částku zdaněnou podle zadaných procent.

Použítím funkcí salary a taxed spočítejte svoji měsíční mzdu po 15% zdanění.

6

Kalkulačka

to dáš

Představte si úplně obyčejnou kalkulačku pouze s tlačítky pro čísla, čtyřmi základními operacemi a tlačítkem pro rovná se. Pokud na takové kalkulačce chcete spočítat něco velmi jednoduchého, například 2 + 3, musíme stisknout tlačíko 2, poté +, pak 3 a pak =. Kalkulačka tedy nespočítá náš výsledek ve chvíli, kdy mačkáme +, ale až ve chvíli, kdy mačkáme =. Musí si tedy zapamatovat, co jsme namačkali, a všecho spočítat až ve chvíli, kdy stiskneme =.

Napište funkci calc se třemi parametry num1, op a num2, které představují první zadané číslo, zadanou operaci jako řetězec a druhé zadané číslo. Operace může být '+', '-', '*' nebo '/'. Funkce vrátí výsledek výpočtu pro zadanou operaci.

Příklad použití

> calc(2, '+', 3)
5
> calc(3, '*', 7)
21
> calc(10, '/', 4)
2.5
7

Ceník

zapni hlavu

Stáhněte si základ stránky, která nabízí předplatné za nějaké služby. Může jít například o internetovou televizi, pravidelné dovážky jídla nebo třeba webový hosting.

  1. Napište funkci selectPlan s jedním parametrem planNumber. Tento parametr bude představovat číslo plánu. Funkce podle čísla plánu vybere ze stránky správný DOM element a přídá k němu CSS třídu plan--selected. Vyzkoušejte vaši funkci v konzoli s různými čísly.
  2. Opakovaným voláním funkce selectPlan lze na stránce postupně vybrat všechny plány. My bychom však chtěli, aby mohl být vybrát vždy nejvýš jeden. Upravte funkci selectPlan tak, že vybere plán zadaný v parametru a u ostatních plánů výběr zruší. Ke zrušení výběru stačí z pčíslušného prvku odebrat třídu plan--selected.

Volitelné úložky na doma

8

Výplňořez

zapni hlavu
  1. Napište funkci fillcut, která jako svůj první parametr str očekává řetězec a jako druhý parametr len kladné celé číslo. Úkolem funkci je oříznout nebo prodloužit zadaný řetězec tak, aby měl délku přesně len.
    • Pokud je vstupní řetězec delší než len, tak funkce odřízne jeho konec a vrátí výsledek.
    • Pokud je vstupní řetězec kratší než len, tak jej doplní od začátku znakem tečky a vrátí výsledek.
    • Pokud je vstupní řetězec dlouhý přesně len, funkce jej vrátí beze změny.

Příklad použití:

> fillcut('petr', 8)
'....petr'
> fillcut('petr', 3)
'pet'
> fillcut('petr', 4)
'petr'
9

Přestupný rok jako funkce

to dáš

Napište funkci isLeapYear, která jako svůj parametr obdrží celé číslo představující rok. Funkce vrátí true, pokud je zadaný rok přestupný. V opačném případě vrátí false.