ES6 v kostce

Nová verze JavaScriptu zvaná ES6 (ECMAScript 6) je k dispozici již skoro 3 roky a za tu dobu se již dostala do všech překladačů (např. Node.js) a i všech moderních prohlížečů.

ES6 nepřináší příliš nových funkčností ale spíše se zaměřuje na to, aby se používání stávajících funkcionalit stalo snažší a aby nebylo potřeba psát tolik kódu (podle DRY). Co všechno tedy přináší nového v porovnání s předchozí verzí?

Jediné prohlížeče, které ES6 nepodporují (jednoduše proto, že jejich poslední verze byla vydána před uvedením ES6 v červnu 2015), jsou Internet Explorer (Edge 12+ ji podporuje), Opera Mini a prohlížeč v Blackberry (a nějaké obskurní prohlížeče jako QQ).

Poznámka: ECMAscript není to samé jako JavaScript. ECMAscript definuje základní pravidla, která pak přebírají jazyky jako JavaScript, TypeScript, apod. Dá se ale říct, že co platí v ES, platí i v JS.

Další poznámka: Výraz ES6 se původně používal pro 6. verzi ECMAscript, ale v současnosti označuje všechny verze následující po ES3 (2000+) a ES5 (2010+), tedy verze 6 (2015), 7 (2017) a 8 (2018).

Proměnné

Kromě stávající definice proměnné pomocí var, která vytvoří proměnnou ve scope funkce můžete vytvořit proměnnou pomocí let, které nadefinuje proměnnou jen v daném bloku – např. IF, FOR, atd.

for (var i = 0; i < 10; ++i) { 
    console.log(i); 
} 
console.log('Cyklus skončil na ', i);  //i = 10 

for (let j = 0; j < 10; ++j) { 
    console.log(j); 
} 
console.log('Cyklus skončil na ', j); 
//vypíše chybu "j is not defined"

Pokud jste zvyklí definovat proměnné na začátku funkce, tak vám tohle, stejně jako mě, asi nepřijde moc přínosné, ale v opačném případě může lépe pomoci odhalit chyby, když se snažíte použít proměnnou, která je již definována, ale má neočekávanou hodnotu.

Konstanty

Třetí možností je definice konstanty pomocí klíčového slova const. Konstanta se chová stejně jako proměnná definovaná pomocí let s tím rozdílem, že do ní lze přiřadit hodnotu pouze při definici a jakékoliv další přiřazení vyvolá chybu „Assignment to constant variable„. Naopak pokud použijete const a neurčíte hodnotu, dostanete chybu „Missing initializer in const declaration„.

Konstanta může obsahovat všechny typy stejně jako normální proměnné a při jejich definici je také možné používat všechny možnosti jazyka jako jsou matematické výpočty, volání funkcí, atd.

Důležité je uvědomit si, že pokud konstanta obsahuje pole nebo objekt, nelze do ní sice přiřadit jiné pole nebo objekt, ale bez problémů lze měnit prvky pole a vlastnosti objektu!

const 
    a = [1,2,3], 
    o = {a: 1, b: 2, c: 3}; 

a[1] = 9; //a === [1, 9, 3] 
o.b = 9;  //o === {a: 1, b: 9, c: 3}

Scope konstanty je stejný jako u let, takže např. uvnitř cyklu můžete do konstanty přiřadit novou hodnotu v každé iteraci (ale jen jednou).

for (let i = 0; i < 10; ++i) {
     const power = Math.pow(2, i); //lze
//   power = 0; //nelze, vyhodí chybu
     console.log('Další mocnina 2 je', power);
 }

Konstantu ale nelze použít pro řídící proměnnou cyklu FOR, protože tu měníte sami:

//NEPOUŽÍVAT!!!! 
for (const i = 0; i < 10; ++i) {
     console.log(i);
     //vypíše pouze 0 a pak chybu, že nejde změnit 
}

Klíčové slovo const se také hodí pro vytváření funkcí metodou přiřazení anonymní funkce do proměnné. Díky tomu, že funkci nadefinujete jako konstantu, zamezíte tomu, aby jste si ji později nechtěně přepsali:

//starý zápis
var process = function(input) { ...; return result; }

//ES6
const process = (input) => { ...; return result; }

Stejně můžete přiřadit i výsledek IIFE (samo-spustitelné funkce), který by se také neměl měnit:

//starý zápis IIFE
var isOk = (function() { ...; return status;})();

//ES6 a IIFE
const isOK = (() => { ...; return status;})();

Destrukturalizace objektů

Při definici proměnných (var, let a const) můžete vlastnosti objektu přiřadit do jednotlivých proměnných. Pokud používáte PHP, tak jistě znáte konstrukci list(), která funguje podobně:

var user = {id: 123, name: "john", age: 30}

//vytvoří proměnné id a name a naplní je z user: 
var {id, name} = user; 

//vytvoří proměnnou firstName se jménem z user: 
var {name: firstName} = user;

Proměnné můžete buď pojmenovat stejně jako původní vlastnosti objektu nebo je přejmenovat uvedením dvojtečky a nového jména.

Stejný zápis samozřejmě můžete použít u v případě, kdy na pravé straně (za rovnítkem) je volání funkce, která vrací objekt.

Získat z objektu můžete i hlouběji zanořenou vlastnost tím, že uvedete její cestu tak, jako byste vytvářeli daný objekt:

//starý zápis 
var name = getUser().default.firstName; 

//ES6 
var { details: { firstName: name }} = getUser();      //vytvoří pouze proměnnou name

Pokud vlastnost v objektu neexistuje, nastaví se příslušná proměnná na hodnotu undefined. Vy ale můžete zadat vlastní výchozí hodnotu:

var 
    user = {},
    { name = 'Unknown', age = 0} = user
;

Objekt můžete destrukturovat i pokud je uveden jako parametr funkce. Daná funkce pak bude mít více parametrů, než kolik jich předáte:

function createUser(id, {name: firstName, age}) {
     return {id, firstName, age};
}
var 
    simpleUser = {name: 'john', age = 30},
     user = createUser(123, simpleUser) 
;

Destrukturalizace polí

Stejně jako u objektů můžete vytvářet proměnné z prvků pole:

var
     a = [1,2,3],
     [b, c, d] = a //b == 1, c == 2, d == 3
;

Pokud chcete některé prvky pole přeskočit, stačí uvést prázdné jméno (tedy dvě čárky za sebou). Také můžete zadat výchozí hodnotu pro indexy, které v poli nejsou definovány:

var 
     a = [1,2,3],
     [b, , c, d = -1] = a //b == 1, c == 3, d == -1
;

Díky tomuhle zápisu můžete snadno prohodit dvě proměnné tím, že z nich vytvoříte pole a pak ho destrukturalizujete (tento zápis je ale pomalejší než s pomocnou proměnnou!):

var  a = 1, b = 2; 

//starý zápis - rychlejší
var tmp = b;
b = a;
a = tmp;

//ES6 - pomalejší, ale přehlednější
[a, b] = [b, a]; //a == 2, b == 1

Stejně jako objekt můžete pole destrukturovat, pokud je zadáno jako parametr funkce:

function sum([a, b = 0, c = 0, d = 0]) {
     return a + b + c + d; 
} 

var arr = [1, 2, 3]; 

console.log(sum(arr)); // vypíše 6 (tedy 1+2+3)

Definice řetězce

Kromě klasické definice řetězce pomocí jednoduchých a dvojitých uvozovek můžete použít definici template pomocí znaku backtick (česky těžký akcent), který můžete znát z Linux konzole nebo MySQL. Na české klávesnici ho napíšete stiskem pravého ALT a ý (resp. číslo 7).

Template má oproti klasickému řetězci dvě výhody: 1) může obsahovat odřádkování (které pak do řetězce vloží znak \n) a 2) může obsahovat proměnné podobně jako dvojité uvozovky v PHP a výrazy podobně jako Linux konzole.

var
     a = 5,
     example = `Tenhle text může být na více řádkách, obsahovat znak backtick: \`, vkládat proměnné: ${a} a výsledky výrazů: ${Math.max(3, a)}`;

Zajímavou vlastností je to, že JavaScript kompilátor musí být schopen odlišovat scope samotné template a vnitřního výrazu, takže template uvnitř výrazu není potřeba escapovat (resp. by to bylo špatně):

//tohle je potřeba escapovat: 
var equal = eval('5==\'5\'?\'OK\':\'Chyba\''); 

//tady žádný escape potřeba není: 
var equal = `${5 == `5` ? `OK` : `Chyba` }`;

Další možností použití template je předání parametrů do funkce (tzv. tagged template), která se pak zavolá místo vyhodnocení řetězce:

function equal(strings, number, string) {
     if (number == string) {
         return strings[0];
     }
     else {
         return strings[1];
     }
}

//tohle je vážně podivný zápis (ale je správně): 
var result = equal`OK${2+3}Chyba${`5`}.`;
console.log( result ); //vypíše OK protože 2+3 == "5"

Funkce jako první parametr dostane pole všech řetězců, které byly v template nalezeny (zde „OK“, „Chyba“ a „.“) a následně výsledek každého výrazu jako jeden parametr (zde 5 a „5“).

První parametr má ještě skrytou vlastnost strings.raw[], ve kterém jsou uloženy řetězce přesně tak, jak byly zadány včetně escapovacích znaků.

Definice objektu

Objekt definujete klasicky {vlastnost: hodnota}, kde jako hodnotu můžete uvést proměnnou. U ES6 ale již nemusíte uvádět jméno vlastnosti, pokud ji chcete uložit do stejné vlastnosti jako je jméno proměnné:

var 
    name = 'john', 
    age = 30, 
    id = 123,
    user = {id, name, age} 
;
//user == {id: 123, name: "john", age: 30}

Pokud chcete zkopírovat vlastnosti jednoho objektu do druhého, musíte je nejprve uložit do proměnných:

var user == {id: 123, name: "john", age: 30}; 

let {name, age} = user; 

var simpleUser = {name, age};
//simpleUser == {name: "john", age: 30}

Pokud naopak potřebujete jméno vlastnosti vypočítat nebo zadat z proměnné, v předchozí verzi JavaScriptu bylo potřeba nejprve vytvořit objekt a pak teprve do něj vkládat vlastnosti. Nyní stačí jméno vlastnosti uvést do hranatých závorek:

//původní zápis 
var 
    name = 'name', 
    role = 'admin',
    o = { id = 123 };

o[name] = 'john';
o['right_' + role] = true;

//ES6
var
    name = 'name',
    role = 'admin',
    o = {
         id = 123,
         [name] = 'john',
         ['right_' + role] = true
    }
;
//o == {id: 123, name: "john", right_admin: true}

Funkce (metody) můžete v objektu vytvářet přímo bez použití přiřazení a klíčového slova function. Vytvářet můžete i virtuální vlastnosti, které volají funkci (getter a setter). Tento zápis je shodný v definicí třídy z ES6 (viz dále) a proto ho zde uvádím pro úplnost, ale pro objekty byl nadefinován již v ECMAscript 5.1.

//původní zápis 
var list = {
     items: [],
     add: function(item) {
         list.items.push(item);
     },
     count: function() {
         return list.items.length;
     }
};

//zkrácený zápis 
var list = {
     items: [],
     add(item) { list.items.add(item) },
     get count() { return list.items.length },
}
//poznámka: původní zápis má volání list.count();
//zatímco zkrácený jen list.count;
//nejde tedy o zcela ekvivalentní zápis

Roztažení (spread operator)

ES6 také podporuje operátor pro roztažení parametrů stejně jako třeba PHP 5.6 (kde se to nazývá argument unpacking). Operátor můžete využít jak při definici proměnné typu pole nebo objekt, tak i při volání funkcí.

Poznámka: Operátor roztažení pro objekty byl dokončen až ve verzi ECMAscript 2018, takže některé starší kompilátory a prohlížeče (před rokem 2018) ho nepodporují!

Volání funkce s parametry v poli

Dříve, pokud jste měli všechny parametry v poli (např. arguments) a chtěli jste s nimi zavolat funkci, museli jste použít metodu apply. Nyní již nemusíte:

(function() {
     //starý zápis:
     myFunc.apply(window, arguments);

     //nový zápis:
     myFunc(...arguments)
})(1,2,3,4,5);

//složení parametrů z více polí: 
myFunc(1, 2, ...arr1, ...arr2);

Pokud jako roztažený parametr použijete objekt, budou všechny jeho vlastnosti předány do funkce ve stejném pořadí, jako kdyby jste je získávali cyklem FOR-IN.

Poznámka: pamatujte, že uvnitř šipkové funkce není pole arguments k dispozici, takže na použití výše uvedeného kódu je potřeba zapsat celé function() {...} místo ()=>...!

Nově lze díky operátoru roztažení použít pole i pro konstruktory, což dříve nešlo (zkombinovat operátor new a metodu apply()):

//Ajax handler: 
function(response) {
     var user = new User(...response.user);
}

class User {
     constructor(id, name, age) {
         //...
     }
}

Připojení pole (Concat)

Operátor roztažení můžete použít pro navazování polí jednoduše tak, že druhé pole uvedete do prvního s použitím operátoru:

//vložení jednoho pole do druhého 
var
     arr1 = [1, 2, 3, 4, 5],
     arr2 = [0, ...arr1, 6]
; //arr2 == [0, 1, 2, 3, 4, 5, 6]

//merge dvou polí
var
     arr1 = [1, 2, 3],
     arr2 = [4, 5, 6],
     mergeArr = [...arr1, ...arr2]
; //mergeArr == [1, 2, 3, 4, 5, 6]

Spojení objektů (Merge)

U objektů operátor roztažení vkládá indexy ve stejném pořadí jako jsou definovány v jednotlivých objektech a pokud se opakují, tak přepíše jejich hodnoty:

var
     user = {id: 123, name: 'john', age: 30},
     employee = {id: 456, name: 'doe',
                 salary: 5000},
     person = {...user,
               ...employee,
               id: undefined
     }
;
//person == {
//    id: undefined,
//    name: 'doe',
//    age: 30,
//    salary: 5000}

Všimněte si, že v objektu person je id jako první, protože bylo první v objektu user, ale má hodnotu undefined, což jsme definovali na konci objektu person. Stejně tak vlastnost name je 'doe', protože hodnota z employee přepsala tu z user.

Kopírování pole

Ve výchozím přiřazení se pole předávají referencí, což znamená, že přiřazení pole do nové proměnné a následná změna, změní i pole původní. Pokud chcete vytvořit nezávislou kopii, můžete k tomu použít operátor roztažení:

//uložení reference bez kopie
var
    a = [1, 2, 3],
    b = a
;
b[1] = 5; // a == [1, 5, 3]

//vytvoření kopie pole
var
    a = [1, 2, 3],
    b = [...a]
;
b[1] = 5; //a == [1, 2, 3]

Toto ale platí jen pro primitivní typy. Pokud jsou v poli objekty nebo jde o víceúrovňové pole, druhá úroveň se opět předá referencí:

//vytvoření kopie pole s objekty
var
    a = [{x:1}, {y:2}, {z:3}],
    b = [...a]
;
b[1].y = 5; //a == [{x:1}, {y:5}, {z:3}]

Export a import

Proměnné (včetně objektů a tříd) můžete vkládat do souboru z jiného pomocí klíčových slov import a export. Ta nahrazují dříve používané konstrukce CommonJs (module.exports) a AMD (define()). Skripty s export se nazývají moduly.

Pojmenované exporty

Pokud chcete použít proměnnou definovanou v jiném souboru, musíte určit jméno proměnné a jméno souboru (ideálně relativně k současnému souboru) pomocí import ... from '...':

import { arr1, obj1, f1, class1 }
         from 'variables.js';

Jméno souboru může začínat bez lomítka nebo s ‚./‚ (aktuální složka nebo podsložka) nebo ‚../‚ (nadřazená složka). Přípona může, nemusí nebo nesmí být uvedena – záleží to na prostředí, ve kterém budete skript spouštět (Node.js, prohlížeč, apod.). V některých prostředích můžete např. importovat proměnné z HTML souborů (viz dále). Jméno souboru (modulu) musí být definováno jako statický řetězec, nejde použít template `...`, proměnné, volání funkce, apod. (viz cyklické importy).

V daném souboru pak požadované proměnné označíte pomocí slova export:

//export nové proměnné a funkce
export const arr1 = [1, 2, 3];
export let obj1 = { a: 1, b: 2};
export function f1() { //... }

//export dříve definované proměnné nebo třídy
var f2 = function() { //... };
export {f2};

class User { //... }
export {User}; 

//Export pod jiným jménem
export {f2 as process};
export {User as MyUser};

//export více proměnných najednou
export {arr1, obj1, f1, f2 as process, User}; 

Importované proměnné můžete také přejmenovat:

import { arr1 as a, obj1 as o }
              from 'variables.js';

Všimněte si, že pokud exportujete dříve definovanou proměnnou (funkce, třídu, apod.), musíte ji uvést do složených závorek. Stejně tak musíte danou proměnnou uvést do složených závorek za klíčovým slovem import. Uvedení export jmeno je neplatná syntaxe!

Výchozí export

Aby JavaScript umožňoval importování tříd podle OOP (tedy jméno souboru se má jmenovat stejně jako třída, kterou definuje, a každý soubor má definovat jen jednu třídu), můžete použít výchozí export, který určí, co je hlavní exportovaná proměnná (nebo třída) daného souboru:

//Database/User.js
class User { //... }
export default User;

//main.js
import DbUser from 'Database/User';

Všimněte si, že výchozí import se liší tím, že jméno neuvádíme do složených závorek! Ve výchozím importu určujete pouze jméno, pod kterým se uloží do aktuálního skriptu, ale nemusí být stejné jako jméno v původním souboru.

Poznámka: výchozí export je ve skutečnosti export proměnné pod jménem default.  Pokud tedy použijete výchozí export a zároveň vyexportujete další proměnnou pod jménem default, dojde k jejich přepsání. Stejně tak zápis import name from ... je zkrácené import {default as name} from ....

//value.js
var default = 2, value = 3;

export default 1; //import value == 1
export default;   //import value == 2
export {value as default}; //import value == 3

Moduly nejsou globální

Pokud skript vložíte jako modul, jeho globální scope bude oddělen a globální proměnné a funkce vytvořené v modulu se neuloží do globálního prostoru (namespace) aktuálního skriptu.

// ..........  lib.js ..............
// globální funkce
function process() { ... }
// globální proměnná
var result = {};
//export
export {process as libProc, result as libRes};

// .......... main.js ..............
import {libProc, libRes} from 'lib';

console.log(process); //undefined, není globální
console.log(result);  //undefined, není globální

Skripty, které tedy obsahují exporty a očekává se, že se budou načítat pomocí import, nemusíte obalovat do self-invoking funkce, abyste zabránili zamoření globálního scope:

// .......... lib.js ..............
;(function() { //není potřeba pro modul
    function process() { ... }
    var result = {};
    export {process as libProc,
            result as libRes};

})(); //není potřeba pro modul

Pokud byste chtěli něco uložit přímo do globálního namespace, můžete použít klíčové slovo daného prostředí (např. window v prohlížeči, GLOBAL v Node.js, apod.). Ukládat ale data do globálního prostoru není příliš dobrá technika (ve spojení s moduly).

Předávání importů

Exportovat můžete i dříve importované proměnné, takže jednotlivé soubory mohou mezi sebou sdílet data:

//Database.js
import connection from 'Connection';
import {server} from 'Config';

connection.connect(server);

export default connection;
export server;

//main.js
import Db, {server} from 'Database';

try {
    Db.query('SELECT ... ');
} catch (e) {
    console.log('Failed query to', server);
}

Všimněte si, že z jednoho souboru můžeme importovat současně výchozí proměnnou a další pojmenovanou (které jsme navíc obě naimportovali každou z jiného souboru).

Pokud chcete rovnou exportovat proměnné z jiného souboru, můžete použít speciální konstrukci export {...} from '...' nebo pro úplně všechny proměnné export * from '...':

//beta.config.js
export * from 'production.config.js';
export {services, plugins} from 'framework';

import {server} from 'production.config.js';
server = 'beta.' + server;
export server;

Cyklické importy

Pokud máte dva provázané soubory, můžete je mezi sebou navzájem naimportovat podle potřeby. To je díky tomu, že JavaScript nejprve projde kód a zkompiluje ho a teprve následně ho spouští. Proto také musí být jméno souboru nadefinováno jako statický řetězec, protože v době, kdy se jméno vyhodnocuje a soubor se načítá, ještě nedochází k vyhodnocení obsahu proměnných ani výsledků funkcí nebo templatů.

//Database.js
import connection from 'Connection';
import {server} from 'main';

connection.connect(server);

export default connection;
export server;

//main.js
export var server = 'localhost';
import Db from 'Database';

try {
    Db.query('SELECT ... ');
} catch (e) {
    console.log('Failed query to', server);
}

Export knihovny

Pokud máte knihovnu, která exportuje více funkcí, nemusíte je importovat podle jmen, ale můžete si je všechny uložit do jedné proměnné:

//Numbers.js
export toBinary = function(i) { ... }
export toHexa = function(i) { ... }
export const PI = 3.14;
//... atd.

//main.js
import * as Num from 'Numbers';
var bits = Num.toBinary(255),
    circle = Num.PI * 1 * 1;

Importy v HTML

V prostředích typu Node.js fungují importy tak, že zatímco se čeká na načtení a zkompilování importovaného souboru, pozastaví se současný skript, ale místo něj může běžet jiný skript.

V HTML probíhá ale všechno synchronně, takže zatímco se čeká na stažení souboru, stojí provádění JavaScriptu i renderování DOMu.

Aby bylo možno zpracovávat importy asynchronně, můžete použít nový HTML tag module (nebo <script type="module">:

<script type=module>
    import $ from 'jquery';
    $('.highlight').css('background', 'yellow');
</script>
<script type=module>
    import $ from 'jquery';
    $('.highlight').css('background', 'yellow');
</script>

Modul se zpracovává stejně jako deferred skript, tedy je spuštěn až v okamžiku, kdy nemá prohlížeč nic jiného na práci. Modul v HTML má pak samozřejmě i ostatní vlastnosti ES6 modulu, jako že nezamořuje svými proměnnými globální namespace window.

Pokud chcete, aby se modul spustil ihned jak bude zpracován (nebo stažen), můžete přidat async:

<script async type=module">
    import $ from 'jquery';
    $('.highlight').css('background', 'yellow');
</script>
<script async type=module>
    import $ from 'jquery';
    $('.highlight').css('background', 'yellow');
</script>

Kód modulu lze také stáhnout ze souboru a dokonce existuje i alternativa pro prohlížeče, které moduly ještě nepodporují:

<script type=module src="highlight.js" />
<script nomodule src="highlight.js">

Výhoda modulu je v tom, že pokud se vám ho podaří do HTML vložit vícekrát (např. z různých šablon nebo přes AJAX), spustí se vždy jen jednou (podobně jako import_once v PHP) – alespoň teoreticky, některé verze prohlížečů Firefox a Safari mohou spustit současně modul a jeho nomodule náhradu a Edge zatím spouští moduly stejně jako skripty, tedy tolikrát, kolikrát je najde v kódu.

Moduly se pro urychlení stahují bez odesílání COOKIES. Pokud ale potřebujete poslat COOKIE pro přihlášení, můžete přidat atribut crossorigin="use-credentials".

Cykly

For each

Stávající javascript již nabízí cyklus FOR-IN, který do proměnné ukládá indexy. Nově ale můžete použít FOR-OF, který naopak ukládá hodnoty (a více tak odpovídá FOREACH z PHP nebo $.each z jQuery). V případě, že procházíte pole nebo objekt a nezáleží vám na indexech nebo jménech proměnných, je tohle lepší volba:

var arr = [1,2,3];

for (const i in arr) {
    console.log(arr[i]); //musíme číst z pole
}

for (const value of arr) {
    console.log(value); //máme přímo hodnotu
}

Oba zápisy (FOR-IN a FOR-OF) mohou iterovat pole (vrací prvky), objekty (vrací vlastnosti a funkce) a řetězce (vrací jednotlivé znaky).

Všimněte si, že u cyklů FOR-IN a FOR-OF má konstanta platnost pouze pro jednu iteraci – toto je doporučený postup, abyste si během cyklu neměnili hodnotu iterace a uvědomili si, že u FOR-OF nelze změnit hodnotu původní proměnné, kterou iterujete.

Funkce

O šipkových funkcích a yield generátorech jsem již psal dříve.

Rest operátor

Spread operátor můžeme použít pro předání parametrů do funkce z pole. Rest operátor vypadá stejně, ale funguje opačně (stejně jako variadické funkce v PHP 5.6):

var myFunc = function(...args) {
    for (arg of args) {
        //...
    }
};

myFunc(1,2,3,4,5);

Výhoda rest operátoru oproti použití arguments je v tom, že rest operátor vytvoří skutečné pole (zatímco arguments je jen objekt s vlastností length a nelze tedy na něj použít metody pole) a navíc je dostupný i v šipkových funkcích (což arguments není).

Samozřejmě lze použít i v konstruktoru:

class User {
    constructor(...values) {
        this.values = [];
        for (value of values) {
            this.add(value);
        }
    }
    add(value) { this.values.push(value); }
}

Výchozí hodnota parametru

Spousta JS funkcí provádí něco podobného:

//starý zápis
function(list, options) {
    list = list || [];
    options = options || {};
    return list.map(function(i) {
        return options.prefix +
                   i + options.suffix;
    }
}

Tedy zajištění, že vstupní parametry budou vždy definované na očekávaný typ (string, array, object, apod.), abychom při každém přístupu k jejich vlastnostem nebo metodám nemuseli ověřovat, zda je to možné.

ES6 tohle usnadňuje pomocí výchozích hodnot pro nedefinované parametry:

function(list = [], options = {}) {
    return list.map(i => options.prefix + i
                       + options.suffix
    );
}

Výchozí hodnota samozřejmě neřeší to, že jako první parametr očekáváte pole, ale programátor může předat objekt nebo string.

Čárky na konci

V ES6 se již nemusíte bát nadbytečných čárek na konci seznamu parametrů (a vlastně i kdekoliv jinde):

function obj(a, b, c,) { //čárka za c je OK
    return {a, b, c,}; //čárka za c zase OK
}
let o = obj(1, 2, 3,) //čárka za 3 je taky OK
//pro úplnost: o == {a: 1, b: 2, c: 3}

Třídy

ES6 konečně implementuje klíčové slovo class (které bylo celé roky rezervované, ale nic nedělalo).

Důležité je uvědomit si, že na rozdíl od proměnných (a funkcí), musí být třída definována dříve než ji použijete! Třídy se tedy chovají podle OOP a ne jako funkcionální JavaScript.

Třídu můžete definovat, stejně jako funkci, dvěma způsoby:

//pojmenovaná globální třída
class User {}

//anonymní lokální třída
let User = class {};

//třída definovaná v modulu
export default class {}

Konstruktor a metody

Třída může (ale nemusí) obsahovat konstruktor (který odpovídá původnímu funkcionálnímu zápisu, kde konstruktorem byla funkce pojmenovaná jako daná třída) a také další metody a dokonce i statické metody:

class User {
    constructor(id, name) {
        this.id = id;
        this.name = name;
    }
    //metody
    login(password) { //... }

    //statické metody
    static factory(user) {
        return new User(user.id, user.name);
    }
}

var
    user1 = new User(1, 'john'),
    user2 = User.factory({id: 2, name: 'doe'})
;

user1.login('123456');

Všimněte si, že metody se nedefinují klíčovým slovem function ale pouze jménem a parametry v závorce. Konstruktor má pak jméno constructor() místo někdy používaného jména třídy.

Poznámka: na rozdíl od jazyků jako je PHP nelze volat statické metody z instance (např. user1.factory()) a statické metody mají proměnnou this nastavenou na undefined, takže její použití vyvolá výjimku (rozdíl oproti původnímu funkcionálnímu zápisu, kde šlo funkci volat i bez operátoru new).

Vlastnosti třídy

ES6 neumí definovat veřejné (public), soukromé (private) ani statické proměnné (jako součást definice třídy).

Veřejné vlastnosti můžete nadefinovat v konstruktoru, statické stejně jako jste byli zvyklí do teď (tedy až po nadefinování třídy):

class User {
    constructor(age) {
        this.age = Math.max(age, User.minAge);
    }
}
User.minAge = 18;

var
    user1 = new User(30),
    user2 = new User(13)
; 

console.log(user1.age, user2.age); // 30 18

user2.age = 13;
console.log(user2.age); // 13

Jediný způsob, jak vytvořit privátní proměnnou je stejný jako dosud pomocí uzávěry v konstruktoru:

class User {
    constructor(age) {
        var _age = Math.max(age, User.minAge);

        this.getAge = () => _age;
        this.setAge = (age) => {
            _age = Math.max(age, User.minAge);
        }
    }
}
User.minAge = 18;

var user1 = new User(30),
    user2 = new User(13); 

console.log(user1.getAge()); // 30

user2.age = 13;
console.log(user2.age, user2.getAge()); // 13 18

Gettery a Settery

Vlastnost třídy můžete vrátit pomocí getteru nebo nastavit pomocí setteru. Problém je ale v tom, že když getter nebo setter neuvedete, nevyvolá se žádná chyba, pokud se o přečtení nebo uložení pokusíte a příkaz se bude pouze ignorovat.

class Dice {
    get value() {
        return Math.round(Math.random()*6+1);
    }
}

var kostka = new Dice();
console.log(kostka.value); //náhodně 1 až 6
kostka.value = 7; //nic nehlásí

Navíc pokud uvedete getter nebo setter, nelze se již dostat ke stejnojmenné veřejné vlastnosti daného objektu, protože jakékoliv zavolání jména vlastnosti znovu vyvolá getter nebo setter (i rekurzivně):

class Dice {
    //NEPOUŽÍVAT!!!
    constructor() {
        this.value = Math.round(
                       Math.random()*6+1);
    }
    get value() {
        return this.value;
    }
    set value(i) {
        throw Error('Nepodváděj!');
    }
}

var kostka = new Dice();
console.log(kostka.value); //stack overflow

Tento kód by fungoval v libovolném jiném jazyce, ale v JavaScriptu dotaz na kostka.value bude dokola volat Dice::value() dokud nespadne. Navíc i nastavení vlastnosti v konstruktoru vyvolá setter, takže hned vytvoření instance vyhodí výjimku!

Gettery a settery se tedy hodí pouze v případě, že chcete vrátit vypočtenou hodnotu nebo část jiné vlastnosti (pole, objekt, apod.):

class App {
    constructor() {
        this.log = [];
    }
    set next(x) { this.log.push(x); }
    get first() { return this.log[0]; }
}

var a = new App;
a.next = 'Start';
console.log(a.first); //'Start'
console.log(a.log);   //['Start']

Gettery a Settery nejsou ale výsada jen tříd, ale můžete je použít i při definici objektů (místo do teď složitějšího zápisu přes Object.defineProperty()). U objektu ale nemůžete používat klíčové slovo this pro přístup k ostatním vlastnostem ale musíte použít uzávěru:

const dice = {
    min: 1,
    max: 6,
    get roll() {
        return Math.round(
           Math.random() * dice.max + dice.min
        );
    }
}

console.log(dice.roll); //náhodně 1 až 6
dice.roll = 7; //ignoruje se

dice.max = 10; //vlastnosti konstanty lze měnit
console.log(dice.roll); //náhodně 1 až 10

Dědění

Třídy v ES6 mohou samozřejmě dědit od předků. Trochu rozdíl je v tom, že dědit lze pouze od tříd a ne již od objektů jako tomu je v případě prototypů. Pokud třídě chcete nastavit jako předka objekt, musíte použít metodu Object.setPrototypeOf().

class Member extends User {
    membershipEnds() { //... }
}

var Dump = {
    dump: function() {
        console.log(this);
    }
}
Object.setPrototypeOf(User.prototype, Dump);

var m = new Member();
m.dump(); //vypíše objekt Member do konzole

Pro zavolání metody předka můžete použít klíčové slovo super stejně jako používáte this:

class VipMember extends Member {
    membershipEnds() {
        return super.membershipEnds() + 90;
    }
}

Pokud potřebujete zavolat konstruktor předka, provede to tak, že super použijete jako metodu (náhražka za dříve používané Class.prototype.constructor.call(this)):

class VipMember extends Member {
    constructor(id, name, extension) {
        super(id, name);
        this.extension = extension
    }
    membershipEnds() {
        return super.membershipEnds() +
                this.extension;
    }
}

Mix-in

ES6 podporuje Mix-iny podobně jako PHP podporuje Traity nebo jazyk C vícenásobné předky. Zápis je ale jiný:

//Definice mixinu VIP,
//... který rozšiřuje třídu Member
var Vip = Member => class extends Member {
    membershipExtension() { return 90; }
}

//Použití mix-inu VIP na třídu Member
class VipMember extends Vip(Member) {
    membershipEnds() {
        return super.membershipEnds() +
             this.membershipExtension();
    }
}

Všimněte si, že mix-iny se zapisují podobně jako funkce (jak jinak, když JavaScript je funkcionální jazyk), kdy nejvnitřnější parametr je třída, od které dědíte a funkce jsou pak mix-iny, které se na ni aplikují.

Jak jsem psal v úvodu, ES6 nepřináší nové funkčnosti ale jen usnadňuje zápis stávajících. Mix-iny tedy fungují tak, že se vezme základní třídy (prototyp objektu) a do ní se překopírují (merge) vlastnosti mix-inu (mergovaný objekt). Znamená to tedy, že pokud třída a/nebo mix-iny obsahují stejné vlastnosti nebo funkce, přepíší se a zůstane platit ta z prvního mix-inu (zleva doprava dle zápisu extend). Je tedy potřeba dát pozor na to, aby mix-iny neměli stejně pojmenované vlastnosti a metody, pokud nechcete, aby se přepsali. Technicky je Mix-in funkce, která má za parametr prototyp třídy a přidá do něj nadefinované metody. Nemá tedy definovaný constructor ani prototype.

Pokud tedy do třídy Employee (zaměstnanec) přidáte mix-iny Children (děti) a Vacation (Dovolená), tak nemůžou oba definovat metodu getCount() (počet dětí a počet dní dovolené), protože pak by fungovala vždy jen jedna z nich. Můžete ale k mix-inu Children přidat mix-in DependentChildren, která metodu getCount() přepíše tak, že bude vracet jen počet nedospělých dětí (na které zaměstnanec dostává úlevy).

Důležité je ale uvědomit si, že zatímco super funguje na rodičovské třídy (které využívají prototypy), tak na mix-iny nefunguje, protože tam se metody přepisují. V příkladu z předchozího odstavce tedy nemůžete použít v metodě DependentChildren::getCount() konstrukci super.getCount(), protože „rodičovská“ metoda byla nadefinována v mix-inu Children a již k ní tedy nevede žádná reference. V nové metodě tedy budete muset zduplikovat kód původní metody pro výpočet všech dětí (a pak odečíst ty již dospělé).

Stejně tak na Mix-iny nefunguje metoda isInstanceOf().

Promise

Velký přínos do asynchronního zpracování JavaSkriptu přináší Promise (česky sliby) a nahrazují dříve používané callbacky a nepřehledné kódy, které vytvářejí:

//starý zápis s callbacky
function doSomethingLong(callback) {
    var data = {};
    //...do something with data
    callback(data);
}
function doSomethingEvenLonger(data, callback) {
    //...do something with data
    callback(data);
}

doSomethingLong(function(response) {
    doSomethingEvenLonger(response,
            function(response) {
         console.log(response);
    });
});
//starý zápis s promisy
function doSomethingLong() {
    var data = {};
    return new Promise(resolve => (
        //...do something with data {
        resolve(data);
    );
}
function doSomethingEvenLonger(data) {
    return new Promise(resolve => (
        //...do something with data {
        resolve(data);
    );
}

//volání Promise pomocí funkcí
doSomethingLong()
    .then(function(response) {
        doSomethingEvenLonger(response)
        .then(function(response) {
            console.log(response);
        });
    })
;
//ten samý zápis se šipkovými funkcemi
doSomethingLong()
    .then(r => doSomethingEvenLonger(r))
    .then(r => console.log(r))
;

Nyní můžete tenhle zápis převést na mnohem jednodušší pomocí klíčových slov async a await:

async function doSomethingLong() {
    var data = {};
    //...do something with data
    return data;
}
async function doSomethingEvenLonger(data) {
    //...do something with data
    return data;
}

var
    data = await doSomethingLong(),
    result = await doSomethingEventLonger(data)
;
console.log(result);

V novém kódu si všimněte, že se pořadí zápisu řádek a proměnných nijak neliší od zápisu, kde vše probíhá synchronně – funkce vrací hodnoty přes return, návratové hodnoty ukládáme do proměnných jednu za druhou atd.

Čím se liší, je právě použití slova async pro definici funkcí, čímž kompilátoru řekneme, že funkce nebude vracet hodnotu synchronně, ale že synchronně vrátí pouze Promise a hodnota bude vrácena asynchronně. Následně při zavolání funkce použijeme slovo await, čímž pozastavíme provádění skriptu do doby, než volaná funkce vrátí hodnotu a dokončí tak Promise.

Důležité je uvědomit si, že zatímco u asynchronního zápisu (callback, promise) po zavolání asynchronní funkce pokračovala dál hlavní funkce a teprve po skončení se zavoval callback nebo reakce na promise, tak u await se výkon hlavní funkce zastaví, dokud asynchronní funkce nehrátí hodnotu. V řeči vláken tedy await odpovídá metodě Thread.join(). Pokud tedy budete chtít přepsat starý asynchronní kód na nový a async a await, je potřeba si dát pozor na to, aby se funkce nevolali v jiném pořadí, než je potřeba pro správnou funkčnost!

Rozdíl oproti normálnímu synchronnímu volání je v tom, že zatímco se čeká na Promise (ať už starým zápisem nebo přes await), mohou se provádět ostatní činnosti (např. v prohlížeči se může renderovat DOM, mohou se provádět skripty zadané s async nebo type=module, atd.).

Pokud voláte několik vnořených asynchronních funkcí v sobě, je potřeba si uvědomit, že různým použitím await a async můžete dosáhnout různých posloupností spouštění:

async load(url) { ... } //stáhne data ze serveru

getSync(url) { //tahle funkce je synchronní
    return await load(url); 
}

async getAsynch(url) { //tahle funkce je asynchronní
    return await load(url);
}

async getPromise(url) { //funkce je asynchronní,
    return load(url);   //ale nečeká a vrací Promise
}

//volání
let 
    a = getSync('a.html'),
    b = getAsynch('b.html'),
    c = await getAsync('c.html');
    d = await getPromise('c.html');

b = b.then(b => b);
d = d.then(d => d);

Z uvedených funkcí se hodnoty a a c načtou synchronně (budou tedy rovnou obsahovat odpověď), zatímco b a d budou obsahovat Promise a odpověď se načte až v okamžiku zavolání then(), které jen překopíruje výsledek.

Pokud chcete čekat na více asynchronních volání, musíte použít metodu Promise.all():

async function doSomething() {
    //...do something
}
async function doSomethingElse() {
    //...do something different
}

await Promise.all(
          doSomething(),
          doSomethingElse()
);
console.log('All done');

Po použití await na Promise.all() dostanete jako návratovou hodnotu pole s výsledky jednotlivých funkcí. Můžete pak tedy použít destrukturalizaci pole (viz výše) pro jejich uložení.

Důležité je uvědomit si, že pokud async metodu zavoláte bez slova await, vrátí svůj Promise. Následně můžete použít await přímo na Promise a počkat na něj:

async function doSomething() { ... }

let promise = doSomething();
promise.then(r => console.log(r));
let result = await promise;
//uloží výsledek a zároveň ho zapíše do konzole

Čísla

Při zadávání čísel můžete rovnou zadat binární nebo octalové (osmičkové) číslo:

//ES5
var binary = parseInt('0011', 2),
    octal  = 0123;
//strict mode
var binary = parseInt('0011', 2),
    octal  = parseInt('123', 8);
//ES6
var binary = 0b0011,
    octal  = 0o123;

Řetězce

ES6 vylepšuje podporu pro unicode znaky v regulárních výrazech a funkcích pro práci s řetězci.

Pro práci s řetězci přidává ES6 nové funkce:

Pad

Funkce padStart() a padEnd() mohou prodloužit řetězec, pokud není dostatečně dlouhý. Snadno tak vyřešíte např. problém s úvodními nulami nebo minimálním počtem znaků:

Num.toBinary(5).padStart('0', 16); //full word
password.padEnd(' ', 8); //minimálně 8 znaků

//práznou mezeru není potřeba uvádět:
password.padEnd(8); //přidá mezery

Array

Pro práci s poli přináší ES6 následující funkce:

includes()

Když chcete zjistit, jestli pole obsahuje určitou hodnotu, doteď bylo potřeba použít metodu indexOf(), která ale vracela -1 místo false a nulu nebo větší číslo, pokud daná hodnota existuje. Nyní již můžete použít metodu, která přímo vrací True nebo False:

var arr = [1,2,3];

//starý zápis
if (-1 < arr.indexOf(5) { //... }

//nový zápis
if (arr.includes(5)) { //... }

Poznámka: tato metoda byla přidána ve verzi ECMAscript 2017 (7. verze), takže starší prohlížeče vydané v letech 2015 až 2017 ji nemusí obsahovat (i když podporují ostatní ES6 konstrukce).

Sady

ES6 přináší nový datový typ Set (česky sada). Sada funguje podobně jako pole s tím rozdílem, že každá hodnota v ní může být jen jednou. Odpadá tedy nutnost vytváření podobného chování pomocí pole:

//starý zápis s polem
var uniqueArray = [];
uniqueArray.pushIf = function(item) {
    if (!this.includes(item)) {
        this.push(item);
    }
};
uniqueArray.pushIf(1);
uniqueArray.pushIf(1);
console.log(uniqueArray.length); // 1

//nový zápis se sadou
var set = new Set();
set.add(1);
set.add(1);

console.log(set.size); //1

//Sada podporuje řetězení
console.log(set.add(2).add(3).size); //3

Hodnoty můžete ze sady odebírat, přičemž zároveň zjistíte, zda daná položka v sadě byla či nikoliv. Můžete také vymazat celou sadu:

var set = new Set();

if (set.delete(5)) {
    console.log('Položka odebrána');
}
else {
    console.log('Položka nenalezena');
}

set.add(1).add(2).clear();
console.log(set.size); // 0

Pro zjištění, zda sada obsahuje hodnotu, bez toho, abyste jí přidali nebo odebrali, slouží metoda has():

var set = new Set();

if (set.has(5)) {
    console.log('Položka je v sadě');
}
else {
    console.log('Položka nenalezena');
}

Symboly

Pro pojmenování vlastností objektů můžete použít speciální typ Symbol, který je určen k tomu, abyste od sebe mohli odlišit normální a speciální vlastnosti a nedocházelo ke kolizím. Vlastnosti pojmenované symbolem se totiž neukáží při použití iterátoru (FOR-IN, FOR-OF, apod.) a také se nevypíšou při konverzi do JSON. Naopak se ale zkopírují při použití metody Object.assign() a dalších podobných akcích (např. přidání mix-inu do třídy).

Anonymní symboly

Symbol se vytváří zavoláním metody Symbol() a každé takové zavolání vytvoří nový unikátní symbol, takže pro pozdější použití je potřeba si ho uložit do proměnné. Funkci sice můžete předat parametr, ale ten slouží pouze jako popis k výpisu do konzole a to, že dva symboly mají stejný popis neznamená, že jsou stejné.

var password = Symbol('password'),
    user = {
        name: 'john',
        [password]: '123456'
    }
;
console.log(JSON.stringify(user));
    // vypíše '{name: "john"}'
console.log(user[password]);
    //vypíše '123456'
console.log(password);
    //vypíše 'Symbol(password)'

Pro získání všech vlastností uložených pod symbolem můžete použít metodu Object.getOwnPropertySymbols(), která vrací pole symbolů použitých v daném objektu:

//navazuje na předchozí příklad...
var symbols = Object.getOwnPropertySymbols(user);
for (let symbol of symbols) {
    console.log(symbol, user[symbol]);
} //vypíše 'Symbol(password)' '123456'

Globální symboly

Abyste si nemuseli vlastní symboly ukládat do proměnných, můžete použít globální registr, který se ukrývá v metodě Symbol.for() (česky ‚symbol pro něco‚). Pokud zavoláte metodu se jménem symbolu, metoda ho vytvoří a následně vždy vrátí ten samý. Je potřeba ale dát pozor, že tento registr je skutečně globální a je sdílen i se skripty v iFramech, Service Workerech, knihovnách (modulech) atd. Je tedy vhodné pojmenovávat symboly podle toho, kde jsou použity (např. „lib.utils.helperClass.errorState“) nebo stylem Java balíků (např. „com.company.lib.errorState“)

var user = {
        name: 'john',
        [Symbol.for('password')]: '123456'
   }
;
console.log(JSON.stringify(user));
    // vypíše '{name: "john"}'
console.log(user[Symbol.for('password')]);
    //vypíše '123456'

pass = Symbol.for('password');
console.log(Symbol.keyFor(pass));
    //vypíše 'password', tedy klíč symbolu
conso.e.log(Symbol.keyFor(Symbol('xxx')));
    //vypíše undefined, protože xxx není
    //registrován jako globální symbol!

Práce se symboly

Důležitá věc je, že symboly jdou z objektu přečíst stejně jako jeho (veřejné) vlastnosti. Lze je sice využít pro ukládání privátních vlastností, takže se nevypíší do JSON ani v cyklech, ale stále k nim kdokoliv může získat přístup, pokud zná jejich jméno, nebo prostě použije Object.getOwnPropertySymbols().

Kromě výpisu do konzole a převedení metodou toString(), jakákoliv jiná akce se symbolem skončí chybou:

Symbol() + 1; //error
'Unique: ' + Symbol(); //error
'Unique: ' + Symbol().toString;
    // vytvoří 'Unique: Symbol()'

Symboly mohou složit jako konstanty pro speciální případy tam, kde není vhodné použít čísla nebo řetězce (které se dají snadno zapsat jako magická hodnota):

const DEBUG = 'debug';
log(DEBUG, 'info');
log('debug', 'info'); //chybný zápis
   //ale funkce log() to nepozná

const DEBUG_SYMBOL = Symbol.for('LOG_DEBUG');
log(DEBUG_SYMBOL, 'info');
log('LOG_DEBUG', 'info');
    //funkce log() pozná chybný parametr a
    //může vyhodit Error

Speciální symboly

ECMAscript 6 sám definuje některé symboly, které lze použít (nebo je nutno použít) pro vytvoření nějaké speciální funkcionality (podobně jako má PHP magické metody začínající ‚__‚. Tyto symboly jsou uloženy v namespace Symbol.

hasInstance

Symbol Symbol.hasInstance lze použít k vytvoření metody, která bude zavolána, když na objekt použijete operátor instanceof. Objekt se tak může vydávat za potomka nějaké třídy nebo objektu, i když to není pravda a jen simuluje jeho chování. Pamatujte, že v případě třídy je potřeba metodu vytvořit jako statickou!

Např. pokud vytvoříte objekt, který se chová jako seznam uživatelů, takže by mohl být potomkem Array ale není:

class UserList {
    constructor() { this.list = []; }
    get length() { return this.list.length; }
    push(user) { this.list.push(user); }
    static [Symbol.hasInstance](parent) {
        return (parent instanceof Array);
    }
}

var ul = new UserList();
console.log(ul instanceof Array); //true
console.log(ul instanceof User); //false

Iterátor

Další Symbol.iterator slouží k napodobení chování pole, kdy můžete libovolný objekt upravit tak, aby ho bylo možno procházet cyklem stejně jako pole. Metoda uložená pod tímto symbolem musí být nadefinována jako generátor:

class UserList {
    constructor() { this.list = []; }
    get length() { return this.list.length; }
    add(user) { this.list.push(user); }
    static [Symbol.hasInstance](parent) {
        return (parent instanceof Array);
    }
    *[Symbol.iterator]() {
        for (let i in this.list) {
            yield this.list[i];
        }
    }
}

var ul = new UserList();
for (let user of ul) {
    console.log(user);
}

Práce s řetězci

Pro objekty, které obsahují řetězcovou hodnotu, můžete použít symboly match, replace a search, abyste umožnili použití objektu v příslušných metodách:

class MyValue {
    constructor(value) { this.value = value; }
    [Symbol.match](str) {
        return this.value === str;
    }
    [Symbol.replace](str, repl) {
        str.replace(this.value, repl);
    }
    [Symbol.search](str) {
        return str.indexOf(this.value);
    }
}
var val = new MyValue('foo');
console.log('foo'.match(val)); //true
console.log(
    'foobar'.replace(val, 'xxx')); //xxxbar
console.log('xxxfoo'.search(val)); // 3

Primitive

Pro převod vašeho objektu na primitivní (základní) typ můžete použít Symbol.toPrimitive. Daná funkce bude zavolána kdykoliv, kdy bude daný objekt použit v nějaké operaci a jako vstupní parametr dostane jméno typu, na který by se měl převést (funguje tedy podobně jako funkce __toString() v PHP):

class MyValue {
    constructor(value) { this.value = value; }
    [Symbol.toPrimitive](type) {
        if ('string' === type) {
            return 'Value('+this.value+')';
        }
        if ('number' === type) {
            return parseInt(this.value);
        }
        return undefined;
    }
}
var val = new MyValue('15');
console.log(10 + val); //25
console.log('' + val)); //'Value(15)'

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Tato stránka používá Akismet k omezení spamu. Podívejte se, jak vaše data z komentářů zpracováváme..