PHP týden PHP týden
Pouze tento týden až 80% sleva na PHP, Nette, Symfony!
Aprílový black friday tě nenechá v klidu! Až 80 % prémiového obsahu zdarma. Více informací

Lekce 4 - Referenční a hodnotové datové typy v JavaScriptu

JavaScript Objektově orientované programování Referenční a hodnotové datové typy v JavaScriptu

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Vlastnosti objektů a konstruktory v JavaScriptu, jsme si vytvořili svůj první pořádný objekt, byla jím firma. Začínáme pracovat s objekty a objekty jsou referenčními datovými typy, které se v některých ohledech chovají jinak, než typy hodnotové (např. čísla). Je důležité, abychom přesně věděli, co se uvnitř programu děje, jinak by nás v budoucnu mohlo leccos překvapit.

Hodnotové a referenční datové typy

Hodnotové datové typy jsou většinou jednoduché struktury, např. jedno číslo. Většinou se chce, abychom s nimi pracovali co nejrychleji, v programu se jich vyskytuje velmi mnoho a zabírají málo místa. V anglické literatuře jsou často popisovány slovy light-weight. Příkladem jsou proměnné typu number nebo boolean.

Aplikace má operačním systémem přidělenou paměť v podobě tzv. zásobníku (stack). Jedná se o velmi rychlou paměť s přímým přístupem, její velikost aplikace nemůže ovlivnit, prostředky jsou přidělovány operačním systémem. Zjednodušeně můžeme říci, že tato malá a rychlá paměť je využívána k ukládání lokálních proměnných hodnotového typu, i když to JS standard nijak nepřikazuje a v praxi by se o této problematice dala napsat kniha. Hodnotové typy jsou v JavaScriptu někdy synonymum pro tzv. immutable objekty. Ale zatím se těmito věcmi nebudeme trápit :)

Proměnnou hodnotového typu si v paměti můžeme představit asi takto:

Proměnná hodnotového typu v paměti stack v JavaScriptu

Na obrázku je znázorněna paměť, kterou může naše aplikace využívat. V aplikaci jsme si vytvořili proměnnou a, které JavaScript přiřadil typ number. Její hodnota je 56 a uložila se nám přímo do zásobníku. Kód by mohl vypadat takto:

let a = 56;

Můžeme to chápat tak, že proměnná a má přidělenu část paměti v zásobníku (velikosti datového typu number, tedy 64 bitů), ve které je uložena hodnota 56.

Vytvořme si novou jednoduchou třídu, která bude reprezentovat uživatele nějakého systému:

class Uzivatel {
    constructor(jmeno, vek) {
        this.jmeno = jmeno;
        this.vek = vek;
    }

    vratUzivatele() {
        return `${this.jmeno} (${this.vek})`;
    }
}

Třída má 2 jednoduché vlastnosti, konstruktor a metodu vratUzivatele(), abychom uživatele mohli jednoduše vypisovat. V nějakém dalším skriptu, např. opět v obsluha.js, vytvoříme instanci této třídy:

let a = 56;
let u = new Uzivatel("Jan Novák", 28);

Proměnná u s uživatelem je nyní referenčního typu. K uložení instance jsme tentokrát použili let namísto const, protože hodnotu proměnné budeme za okamžik měnit, na uložení proměnné to nemá žádný vliv. Podívejme se na novou situaci v paměti:

Referenční typ na haldě v JavaScriptu

Vidíme, že objekt (proměnná referenčního datového typu) se již neukládá do zásobníku, ale do paměti zvané halda. Je to z toho důvodu, že objekt je zpravidla složitější než hodnotový datový typ (většinou obsahuje hned několik dalších atributů) a také zabírá v paměti více místa.

Zásobník i halda se nacházejí v paměti RAM.

Proměnné referenčního typu jsou v paměti uloženy vlastně nadvakrát, jednou v zásobníku a jednou v haldě. V zásobníku je uložena pouze tzv. reference, tedy odkaz do haldy, kde se poté nalézá opravdový objekt.

Můžete se ptát, proč je to takto udělané. Důvodů je hned několik, pojďme si některé vyjmenovat:

  1. Místo ve stacku je omezené.
  2. Když budeme chtít použít objekt vícekrát (např. ho předat jako parametr do několika metod), nemusíme ho v programu předávat jako kopii. Předáme pouze malý hodnotový typ s referencí na objekt místo toho, abychom obecně paměťově náročný objekt kopírovali. Toto si vzápětí ukážeme.
  3. Pomocí referencí můžeme jednoduše vytvářet struktury s dynamickou velikostí, např. struktury podobné poli, do kterých můžeme za běhu vkládat nové prvky. Ty jsou na sebe navzájem odkazovány referencemi, jako řetěz objektů.

Založme si 2 číselné proměnné a dvě instance třídy Uzivatel:

let a = 56;
let b = 28;
let u = new Uzivatel("Jan Novák", 28);
let v = new Uzivatel("Josef Nový", 32);

Situace v paměti bude následující:

Reference na haldu v JavaScriptu

Nyní zkusme přiřadit do proměnné a proměnnou b. Stejně tak přiřadíme i proměnnou v do proměnné u. Hodnotový typ se v zásobníku jen zkopíruje, u objektu se zkopíruje pouze reference (což je vlastně také hodnotový typ), ale objekt máme stále jen jeden. V kódu vykonáme tedy toto:

let a = 56;
let b = 28;
let u = new Uzivatel("Jan Novák", 28);
let v = new Uzivatel("Josef Nový", 32);
// Přiřazení
a = b;
u = v;

V paměti bude celá situace vypadat následovně:

Ukládání referencí v JavaScriptu

Přesvědčme se o tom, abyste viděli, že to opravdu tak je :) Nejprve si necháme všechny čtyři proměnné vypsat před a po změně. Protože budeme výpis proměnných volat vícekrát, napíšeme si pro něj v obsluha.js jednoduchou funkci. Tentokrát je budeme vypisovat do konzole do přehledné tabulky, kde bude na každém řádku jedna proměnná a ve sloupcích její hodnoty. Upravme tedy kód na následující:

function vypisPromenne() {
    console.table({
            "a": a,
            "b": b,
            "u": u,
            "v": v
        });
}

let a = 56;
let b = 28;
let u = new Uzivatel("Jan Novák", 28);
let v = new Uzivatel("Josef Nový", 32);

vypisPromenne();

// Přiřazení
a = b;
u = v;

vypisPromenne();

Po spuštění v prohlížeči si otevřete vývojářskou konzoli pomocí F12, poté klikněte na záložku Console, kde bude podobná tabulka:

Debug výpis v podobě tabulky do konzole

Na výstupu programu zatím rozdíl mezi hodnotovým a referenčním typem nepoznáme. Nicméně víme, že zatímco v a a b jsou opravdu 2 různá čísla se stejnou hodnotou, v u a v je ten samý objekt. Pojďme změnit jméno uživatele v a dle našich předpokladů by se měla změna projevit i v proměnné u. K obsluze připíšeme:

v.jmeno = "John Doe";

vypisPromenne();

Změnili jsme objekt v proměnné v a znovu vypíšeme u a v:

Reference v JavaScriptu

Spolu se změnou v se změní i u, protože proměnné ukazují na ten samý objekt. Jestli se ptáte, jak vytvořit opravdovou kopii objektu, tak nejjednodušší je objekt znovu vytvořit pomocí konstruktoru a dát do něj stejná data. Připomeňme si situaci v paměti ještě jednou a zaměřme se na Jana Nováka.

Garbage Collector v JavaScriptu

Co se sním stane? "Sežere" ho tzv. Garbage collector.

Garbage Collector

Garbage collector a dynamická správa paměti

Paměť můžeme v programech alokovat staticky, to znamená, že ve zdrojovém kódu předem určíme, kolik jí budeme používat. Doposud jsme to tak vlastně dělali a neměli jsme s tím problém, hezky jsme do zdrojového kódu napsali potřebné proměnné. Brzy se ale budeme setkávat s aplikacemi, kdy nebudeme před spuštěním přesně vědět, kolik paměti budeme potřebovat. V tomto případě hovoříme o dynamické správě paměti.

V minulosti, hlavně v dobách jazyků C, Pascal a C++, se k tomuto účelu používaly tzv. pointery, neboli přímé ukazatele do paměti. Vesměs to fungovalo tak, že jsme si řekli operačnímu systému o kus paměti o určité velikosti. On ji pro nás vyhradil a dal nám její adresu. Na toto místo v paměti jsme měli pointer, přes který jsme s pamětí pracovali. Problém byl, že nikdo nehlídal, co do paměti dáváme (ukazatel směřoval na začátek vyhrazeného prostoru). Když jsme tam dali něco většího, zkrátka se to stejně uložilo a přepsala se data za naším prostorem, která patřila třeba jinému programu nebo operačnímu systému (v tom případě by naši aplikaci OS asi zabil - zastavil). Často jsme si však my v paměti přepsali nějaká další data našeho programu a program se začal chovat chaoticky. Představte si, že si uložíte uživatele do pole a v tu chvíli se vám najednou změní barva uživatelského prostředí, tedy něco, co s tím vůbec nesouvisí. Hodiny strávíte tím, že kontrolujete kód pro změnu barvy, poté zjistíte, že je chyba v založení uživatele, kdy dojde k přetečení paměti a přepsání hodnot barvy.

Můj kolega jednou pravil: "Lidský mozek se nedokáže starat ani o správu paměti vlastní, natož aby řešil memory management programu." Měl samozřejmě pravdu, až na malou skupinu géniů lidi přestalo bavit řešit neustálé a nesmyslné chyby. Za cenu mírného snížení výkonu vznikly řízené jazyky (managed) s tzv. garbage collectorem, jedním z nich je i JavaScript.

Garbage collector

Garbage collector je vlastně program, který běží paralelně s naší aplikací, v samostatném vlákně. Občas se spustí a podívá se, na které objekty již v paměti nevedou žádné reference. Ty potom odstraní. Ztráta výkonu je minimální a značně to sníží procento sebevražd programátorů, ladících po večerech rozbité pointery. Protože je jazyk řízený a nepracujeme s přímými pointery, není vůbec možné paměť nějak narušit, nechat ji přetéct a podobně, interpret se o paměť automaticky stará.

Hodnota null a undefined

Poslední věc, o které se dnes zmíníme, jsou hodnoty null a undefined.

null

Klíčové slovo null označuje, že reference neukazuje na žádná data. Když nastavíme proměnnou v na null, zrušíme pouze tu jednu referenci. Pokud na náš objekt existuje ještě nějaká reference, bude i nadále existovat. Pokud ne, bude uvolněn GC. Změňme ještě poslední řádky našeho programu na:

v.jmeno = "John Doe";
u = null;

vypisPromenne();

Dostaneme:

Hodnota null v JavaScriptu

Vidíme, že objekt stále existuje a ukazuje na něj proměnná u, v proměnné v již není reference.

undefined

Nyní k undefined. Když si vytvoříme nějakou proměnnou, které ovšem nenastavíme hodnotu, bude v ní právě undefined. Zkusme si to:

let promenna;
console.log(promenna); // vypíše undefined

Tato hodnota je také např. na indexech polí, na kterých zatím není žádná hodnota. Označuje tedy, že proměnná existuje, ale nic neobsahuje. To samé by se stalo, pokud bychom vytvořili nějakou funkci s parametrem a parametr při volání nezadali:

function zkouska(parametr) {
    console.log(parametr);
}

zkouska(); // vypíše undefined

null na rozdíl od undefined označuje, že do proměnné již bylo uloženo a byla nastavena právě na prázdnou referenci.

V příští lekci, Tvorba OOP diáře v JavaScriptu, si zas něco praktického naprogramujeme, ať si znalosti zažijeme. Prozradím, že půjde o elektronický diář :)


 

Stáhnout

Staženo 13x (999 B)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

 

Článek pro vás napsal Šimon Raichl
Avatar
Jak se ti líbí článek?
1 hlasů
Autor se věnuje hlavně tvorbě všemožných věcí v JS
Aktivity (2)

 

 

Komentáře

Avatar
Michal Žůrek - misaz:6.12.2018 22:15

Interní datové typy mají v JavaScriptu názvy s velkým počátečním písmenem.

 
Odpovědět 6.12.2018 22:15
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovědět 7.12.2018 11:54
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Jindřich Máca:7.12.2018 13:56

Oba máte pravdu. :-D Konstruktor toho datového typu se píše s velkým písmenem, stringový název je potom s malým. Nejlépe to asi vystihne tenhle příklad z toho odkazu od Davida:

typeof Number('1') === 'number'; // Number tries to parse things into numbers
 
Odpovědět  +3 7.12.2018 13:56
Avatar
Odpovídá na David Čápka
Michal Žůrek - misaz:7.12.2018 22:54

jo, dá se považovat za správnou úvahu, ale to co vrátí typeof není název datového typu. To je jakési simple rozlišení mezi undefined, string, number, boolean, … (dle mého názoru k ničemu, viz. dále). Ani jedno z toho není název datového typu, ale jakási výstupní hodnota tohoto operátoru/funkce.

Demonstruje to pěkně příklad s vlastním datovým typem (např. třídou v ES6, v ES5 to funguje stejně).

class Foo {} // v ES5 se to napíše jako function Foo {}
var x = new Foo();
console.log(typeof(x)); // vypíše "object"

// bonus
console.log(typeof(Foo)); // vypíše "function", i ES6 "třídy" jsou pořád jen "funkce".

i proto se pro porovnání datového typu nepoužívá operátor typeof, ale instanceof.

class Foo {}
var x = new Foo();

console.log(x instanceof Foo); // true
console.log(x instanceof Object); // true
console.log(x instanceof String); // false
console.log(x instanceof string); // error: Datový typ string s malým s neexsituje

Protože JavaScript je jazyk od přírody debilní (a ani ES6 to nespraví), tak existují různé záludnosti, které v této logice úplně nedávají smysl.

console.log("ahoj" instanceof String); // false
console.log("ahoj".toString() instanceof String); // false
console.log(new String("ahoj") instanceof String); // true
console.log(5 instanceof Number); // false

Na svou obhajobu ještě uvedu, že alternativní zdroj https://www.w3schools.com/…atatypes.asp je také uvádí s velkými písmeny.

 
Odpovědět 7.12.2018 22:54
Avatar
Odpovídá na Michal Žůrek - misaz
Michal Žůrek - misaz:7.12.2018 23:02

Ještě přikládám jednu perlu

console.log(String("ahoj") instanceof String); // false
 
Odpovědět 7.12.2018 23:02
Avatar
Odpovídá na David Čápka
Michal Žůrek - misaz:7.12.2018 23:09

V dokumentaci http://www.ecma-international.org/…ECMA-262.pdf jsou popsány typy na straně 16 a jsou také uvedeny s velkými písmeny.

 
Odpovědět 7.12.2018 23:09
Avatar
Šimon Raichl
Překladatel
Avatar
Odpovídá na Michal Žůrek - misaz
Šimon Raichl:8.12.2018 0:17

No tak samozrejme, protoze string jako primitivni typ nemuze byt instanci niceho, a pri volani konstruktoru bez klicovyho slova new ti vrati string, kdyz pouzijes pri volani kostruktoru new, tak uz bude instanci String, protoze ti nevrati string ale objekt. Treba u poli uz toto neplati, at uz zavolas Array s new, bez new, nebo napises jako prazdny pole (coz oba predchozi vraceji, pokud je nechas bez parametru, nebo kdyz je parametr 0), je pole instanci Array a zaroven instanci Object podle JS :D :

Array() instanceof Array // true
new Array() instanceof Array // true
[] instanceof Array // true
[] instanceof Object // true

Jinak co se tyce typeof, tak to taky neni uplne dokonaly :D

typeof null // vrati "object"
 
Odpovědět  +1 8.12.2018 0:17
Avatar
Jan Osuský
Člen
Avatar
Jan Osuský:4. března 16:47

Ahoj,

Vidíme, že objekt stále existuje a ukazuje na něj proměnná "u", "v" proměnné v již není reference.

písmenka "u" a "v" by asi měla být prohozená :).

 
Odpovědět 4. března 16:47
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 8 zpráv z 8.