Nahoru
 

JavaScript, aneb jak postavit dům pomocí sirek

Pokud znáte jazyk JavaScript, možná jste si mohli všimnout, že všelijaké jeho části jsou realizovány trochu nestandardně, oproti „klasikům“ jako je např. C#, C++ nebo Java. JavaScript totiž má jeden velice působivý prvek (objekt), pomocí kterého lze poskládat téměř vše.

Začněme nejprve datovými typy. Ty v JavaScriptu (zkráceně JS) můžeme rozdělit na dva: primitiva a objekty. Primitiva jsou čísla (number), slova (string), boolean (true nebo false), null a undefined (v novějších verzích ještě Symbol). Tyto datové typy se při přiřazení do jiných proměnných celé kopírují. Oproti tomu, objekty jsou referenčním datovým typem, tedy při přiřazování se nekopírují, pouze si přes proměnné předávají referenci.

//PRIMITIVA
//Hodnoty se kopírují
var primitive1 = "Ahoj";
var primitive2 = primitive1 + "!";
console.log(primitive1); //Ahoj
console.log(primitive2); //Ahoj!


//OBJEKTY
//Proměnné si předávají reference,
// objekt je to stále stejný
var object1 = {
    "pozdrav" : "Ahoj"
}
var object2 = object1;
object2.pozdrav = "Ahoj!";
console.log(object1.pozdrav); //Ahoj!
console.log(object2.pozdrav); //Ahoj!

Objekty v JavaScriptu skrývají skutečně velkou moc. Jedná se o vcelku jednoduchou, nicméně velice flexibilní strukturu. Objekty v JS dokážou za běhu sami tvořit nové vlastnosti. Vlastnosti lze dokonce přiřazovat dynamicky, pomocí proměnné a operátoru „[]“. Každá vlastnost objektu má klíč a hodnotu. Klíč si lze představit jako název vlastnosti neboli identifikaci hodnoty. Klíč musí být buď typu String nebo Symbol, ale hodnota může být cokoliv, například další objekt.

var object3 = {
    "key1": 100,
    "key2": false,
    "key3": {
        "key31": "Ahoj!",
        "key32": null
    }
}
console.log(object3.key3.key31); //Ahoj!

//Dynamické přidání vlastností
object3.dyn1 = "Nová vlastnost!";
console.log(object3.dyn1); //Nová vlastnost!

//Můžeme použít i proměnné
var klic1 = "NejakyKlicX";
object3[klic1] = "Další nová vlastnost!";
console.log(object3[klic1]); //Další nová vlastnost!
console.log(object3.NejakyKlicX); //Další nová vlastnost!

Pomocí objektů je realizován v podstatě celý JavaScript, ať už se bavíme o „třídách“ jako pole nebo složitějších strukturách, jako dědění, scope (lexical enviroment), funkce apod. Není tu něco zvláštního? Jak to, že o implementační funkčnost jazyka jako scope nebo funkce, se starají „prachsprosté“ objekty? Jak již bylo zmíněno, objekt je skutečně stavební kámen JavaScriptu a velká část z toho, co denně používají JavaScriptoví programátoři jsou skutečně objekty, někdy však pouze dobře maskované.

Prototypová dědičnost

Co jistě stojí za bližší vysvětlení je prototypová dědičnost. Jak již bylo zmíněno, dědičnost je v JavaScriptu realizována pomocí objektů a funguje zde velmi triviálně. Chtějme volat funkci SayName() na objektu Dog (Dog.SayName()). Pokud daný objekt metodu má, výborně, provede se. Pokud ne, JavaScript začne hledat ve vlastnosti Dog.__proto__, což je další objekt. Pokud ani v tom není vlastnost s funkcí (tedy metoda) SayName(), JavaScript začne hledat ve Dog.__proto__.__proto__. JavaScript hledá, dokud nenarazí na __proto__ rovné null. Dědičnost tedy můžeme realizovat tak, že do vlastností __proto__ budeme vkládat vlastnosti rodičů.

//DEMONSTRACE VLASTNOSTI __proto__
//Třída nějakého zvířete
var Animal = {
    "Name": "Tonda",
    "SayName": function ()
    {
        console.log(this.Name);
    }
}

//Třída nějakého psa
var Dog = {
    "Barf": function ()
    {
        console.log("HUF!");
    }
}

//Nastavíme vlastnosti __proto__ našeho psa
// na zvíře, tedy realizujeme dědičnost
Dog.__proto__ = Animal;

Dog.SayName(); //Tonda

Pozn.: Za poznámku určitě stojí říci, že vlastnost __proto__ se needituje přímo, zvláště kvůli špatné kompatibilitě. Nejlepší je využít funkcí jako například Object.create(proto).

Aby se však JavaScript přiblížil k objektovým jazykům jako C++, C# nebo Java, autoři se rozhodli přidat klíčové slovo „new“ a tzv. konstruktory. Není to však nic jiného, nežli že pomocí operátoru „new“ zavoláme funkci a jako její kontext „this“ nastavíme nějaký nový prázdný objekt. Do tohoto objektu se do __proto__ přiřadí statická vlastnost Dog.prototype (pro konstruktor s názvem Dog). Toto je však na dlouhé povídání, o kterém si můžeme přečíst například na stránkách MDN.

//DĚDIČNOST A KLÍČOVÉ SLOVO NEW
//Konstruktor objektu Animal
function Animal(name)
{
    this.name = name;
}

//Metoda SayName
Animal.prototype.SayName = function ()
{
    console.log(this.name);
}



//Prototype Dog má nyní prázdný objekt s __proto__ 
// namířeného na Animal, tedy dědíme prototype
Dog.prototype = Object.create(Animal.prototype);

//Konstruktor objecktu Dog
function Dog(name)
{
    //Zavolání konstruktoru rodiče
    Animal.call(this, name);
}

//Metoda Barf
Dog.prototype.Barf = function ()
{
    console.log("HUF!");
}

var myDog = new Dog("Pepa");
myDog.SayName(); //Pepa

Pole

Pole v JavaScriptu je velmi zajímavé. Nefunguje totiž jako „normální“ pole (více o fungování pole se dozvíte v našem článku „Jak funguje pole v programování“). Pole je ve skutečnosti objekt. Podle zvyklosti se položky pole selektují pomocí operátoru „[]“. Zde je to však operátor pro vlastnost objektu, tedy ve skutečnosti ukládáme hodnoty do vlastností objektu, nikoliv „nějakého“ pole. Pole je tedy „fancy obalený“ objekt.

Co je na tom ještě bizarnější je to, že vlastnosti nemohou být čísla, proto se indexy [0] přetypovávají na ["0"].

Pole v JavaScriptu díky svým vlastnostem nemusíme realokovat (je triviálně škálovatelné), což je úžasná výhoda. Bohužel, přináší to značnou režii navíc. „Prázdné indexy“, tedy nedefinované vlastnosti, jsou typu „undefined“ (což je obecně každá nedefinovaná vlastnost).

var array = [null, 22, false, 44, "55", 66];

//Přidáme na nové místo
array[6] = "Ahoj!";

//Demonstrace přetypování indexu na string
// (tedy že array je ve skutečnosti objekt)
console.log(array["0"]); //null
console.log(array["6"]); //Ahoj!

Funkce

Pravděpodobně vás nepřekvapí, když vám povím, že funkce je také ve skutečnosti pouhý objekt. My pouze pomocí operátu „()“ spouštíme její obsah (velmi jednoduše řečeno). Jak však tedy fungují argumenty?

Argumenty jsou při volání uvnitř funkce uloženy v pseudo-poliarguments“. Pseudo-pole znamená, že je to indexovaný objekt s vlastností length, takže se dá snadno iterovat, nicméně nemá metody ani vlastnosti objektu Array (tedy normální pole).

function FunkceSJednimArgumentem(arg1)
{
    console.log(arguments[0]); //Ahoj
    console.log(arguments[1]); //Tohle
    console.log(arguments[2]); //Je
    console.log(arguments[3]); //Zajímavé
}

//Tato funkce...
FunkceSJednimArgumentem("Ahoj", "Tohle", "Je", "Zajímavé", "AleTohleUžNe");

//... vypíše:
/*
 * Ahoj
 * Tohle
 * Je
 * Zajímavé
 * */


//Ověření, že je funkce opravdu objekt
console.log(FunkceSJednimArgumentem instanceof Object); //true

JavaScript skrývá nespočet funkčností, z nichž drtivá většina jsou (nebo využívají) objekty. Některé vymoženosti využití objektů dobře skrývají a chovají se velice podobně, jako v ostatních jazycích (např. pole či funkce) a některé jsou zase těžce orientované na čisté práce s objekty (např. dědičnost).

Vnitřní struktura JavaScriptu a to, že většina jeho základů stojí na jednoduchých, avšak flexibilních objektech, je vskutku fascinující. Mohli bychom to přirovnat k domu sestaveného ze sirek. Použití JavaScriptu je však vhodné pouze v některých situacích, zvláště díky drastické neefektivitě a nedostatku nástrojů pro enterprise vývoj. Rozhodně bych jej nedoporučoval pro rozsáhlé back-end systémy nebo jakékoliv „velké“ aplikace. Jeho použití je však velmi jednoduché a pro skriptování a menší projekty či prototypy naprosto ideální.