Reference a hodnota

Je důležité umět rozlišovat mezi hodnotami a referencemi v kódu, protože tahle vlastnost má zásadní vliv na chování vašeho programu.

AndreaAndrea

Hodnoty se týkají především primitivních datových typů a reference zase objektů a polí. Objekty jsou složité datové struktury či pole, které mohou obsahovat další hodnoty, objekty nebo funkce.

Literální hodnota

Začneme nejjednodušším pojmem. Například číslo 2 je samotná hodnota, které se říká literální hodnota, protože je reprezentována sama o sobě nějakým binárním číslem, které je pak uloženo v paměti pod tímto symbolem.

Literály se používají

  • v inicializaci proměnných
  • předání hodnot do volaných funkcí nebo metod Math.round(4.6);
  • používají se jako samostatné hodnoty, například v podmínkách nebo výrazech if (a > '4') {}

Literály jsou nezměnitelné a jejich hodnota se nemění ani po přiřazení do jiné proměnné. V JS existují různé druhy literálů pro různé datové typy, jako jsou:

Číselné literály:

  • 333, 3.14 (desetinné číslo)
  • 1e6 (číslo s exponentem);

Řetězcové literály:

Řetězce jsou uspořádané kolekce znaků, které se obvykle používají k reprezentaci slov a vět. K ohraničení hodnot se používají dvojité " nebo jednoduché uvozovky ". Např.

  • "ahoj", 'ahoj' (jednoradkový řetězec)
  • hello world (víceradkový řetězec)

Template literals umožňují používat proměnné a výrazy uvnitř řetězců bez nutnosti konkatenačních operátorů +. Template literals se zapisují v backticků (`) a výrazy se vkládají do řetězců pomocí závorek s dollarem a hvězdičkou ${expression}.

Například:

const x = 10;
const y = 20;
console.log(`Součet  x a y je ${x + y}.`);
// "Součet  x a y je 30."

Template literals také umožňují používat víceradkové řetězce bez nutnosti použití konstrukce jako je "string1 + \n + string2".

Logické literály:

  • true (pravda)
  • false (nepravda)

Prázdné hodnoty

  • null - hodnota, která představuje absence hodnoty. Například: const x = null;
  • undefined - hodnota, která představuje hodnotu, která nebyla definována nebo přiřazena.

Regex

Regulární výrazy se zapisují jako literály v obdelníkových závorkách /pattern/flags, kde "pattern" je vzor, podle kterého se má hledat, a "flags" jsou volitelné modifikátory, které určují, jak se má vzor chovat.

Kopírování primitivní hodnoty a reference

Při kopírování primitivního datového se vytvoří kopie hodnoty. Originální hodnota zůstává nezměněna a kopie má stejnou hodnotu jako originální, ale jsou to dvě samostatné proměnné, které se neovlivňují navzájem.

Mutabilita

V tomto příkladu má proměnná x hodnotu 10 a její hodnota sa přiřadí do proměnné y.

let x = 10;
let y = x;

console.log(x);  // vypíše: 10
console.log(y);  // vypíše: 10

Následně se hodnota premené x zmení na 20, ale hodnota premenné y sa nezmení a zústává rovná 10.

x = 20;

console.log(x);  // vypíše: 20
console.log(y);  // vypíše: 10

To je proto, že obě dvě proměnné obsahují primitivní hodnotu a když se hodnota jedné změní, druhá se nezmění.

Reference a strukturované data

Je důležité vědět, že pokud přiřadíme objekt do jiné proměnné, tak se nekopíruje samotný objekt, ale pouze adresa (referenční hodnota) na něj.

Použití reference je výhodné hlavně pro šetření velikosti paměti počítače, protože se proměnné sdílí stejné data. Při předávání objektů do argumentů funkce nemusíme kopírovat celé struktury, ale jen adresu.

Nyní se stejně jako v předchozím příkladu podíváme na příklad s objektem. V tomto příkladu má proměnná objx hodnotu objektu s vlastností name s hodnotou "Vývojář". Když se tato hodnota přiřadí do proměnné objy, tak obě proměnné referencují na stejný objekt v paměti.

let objx = { name: "Vývojář" };
let objy = objx;

console.log(objx.name);  // vypíše: "Vývojář"
console.log(objy.name);  // vypíše: "Vývojář"

Následně se hodnota vlastnosti name v objektu změní na "pro Vývojáře" pomocí přiřazeí do vlastnosti objx.name. Protože se obě proměnné odkazují na identický objekt, tak vlastnost objx.name se také změní na "pro Vývojáře".

objx.name = "pro Vývojáře";

console.log(objx.name);  // vypíše: "pro Vývojáře"
console.log(objy.name);  // vypíše: "pro Vývojáře"

Kopírování strukturovaných dat

Je důležité si uvědomit, že pokud chceme zachovat původní hodnoty objektu a přiřadit je do nové proměnné, musíme vytvořit kopii objektu. To se dělá pomocí metody Object.assign() nebo operátoru spread (...).

V tomto příkladě se původní hodnota objektu objx zkopíruje do nového objektu pomocí metody Object.assign()a výsledek se přiřadí do proměnné objy.

let objx = { name: "Vývojář" };
let objy = Object.assign({}, objx);
console.log(objx.name);  // vypíše: "Vývojář"
console.log(objy.name);  // vypíše: "Vývojář"

V tomto bodě se pozastavme nad oběma objekty. Podařilo se nám je naklonovat do dvou proměnných zvlášt, nicméně pokud je porovnáme operátorem === tak i přesto, že jsou hodnoty obou objektů identické, tak dostaneme false. To protože, v případě objektů srovnávacím operátorem porovnáváme reference/adresy v paměti, nikoliv hodnoty.

console.log(objx === objy); // false

Následná změna hodnoty vlastnosti objx.name neovlivní hodnotu vlastnosti objy.name, protože objy obsahuje kopii původního objektu.

objx.name = "pro Vývojáře";
console.log(objx.name);  // vypíše: "pro Vývojáře"
console.log(objy.name);  // vypíše: "Vývojář"

Kopírování vnořených hodnot objektů

Pokud máme objekt, který obsahuje v klíči další složené struktury (objekty, pole) a chceme zachovat původní hodnoty všech jeho vlastností, pak chceme udělat něco čemu se říká hluboká kopie.

let obj1 = {
  name: "John",
  address: {
    city: "Prague",
    country: "Czech Republic"
  }
};

// Chceme zachovat původní hodnoty objektu obj1 a přiřadit je do obj2
let obj2 = JSON.parse(JSON.stringify(obj1));

console.log(obj1.name);  // vypíše: "John"
console.log(obj2.name);  // vypíše: "John"

console.log(obj1.address.city);  // vypíše: "Prague"
console.log(obj2.address.city);  // vypíše: "Prague"

obj1.name = "Peter";
obj1.address.city = "Brno";

console.log(obj1.name);  // vypíše: "Peter"
console.log(obj2.name);  // vypíše: "John"

console.log(obj1.address.city);  // vypíše: "Brno"
console.log(obj2.address.city);  // vypíše: "Prague"

Je důležité si uvědomit, že metody JSON.stringify() a JSON.parse() nefungují na všechny typy dat v JavaScriptu. Například neumí zkopírovat funkce nebo symboly. Pokud chceme zkopírovat objekt s těmito typy dat, musíme použít jiné metody nebo knihovny.

Kopírování funkcí a symbolů

Ukážka použití kopírování objektu s funkcemi a symboly:

let obj1 = {
  name: "John",
  sayHello: function() {
    console.log("Hello!");
  },
  [Symbol("secret")]: "This is a secret"
};

// Chceme zachovat původní hodnoty objektu obj1 a přiřadit je do obj2
let obj2 = { ...obj1 };

console.log(obj1.name);  // vypíše: "John"
console.log(obj2.name);  // vypíše: "John"

obj1.name = "Peter";

console.log(obj1.name);  // vypíše: "Peter"
console.log(obj2.name);  // vypíše: "John"

obj1.sayHello();  // vypíše: "Hello!"
obj2.sayHello();  // vypíše: "Hello!"

console.log(obj1[Symbol("secret")]);  // vypíše: "This is a secret"
console.log(obj2[Symbol("secret")]);  // vypíše: "This is a secret"

V tomto příkladě má objekt obj1 vlastnost name, funkci sayHello a symbol secret. Použitím operátoru spread můžeme zkopírovat hodnoty všech vlastností objektu obj1 do objektu obj2, včetně funkcí a symbolů. Změna hodnoty vlastnosti obj1.name nemá žádný vliv na hodnotu vlastnosti obj2.name a obě funkce sayHello() fungují stejně. Symbol secret také zůstává zachován v obou objektech.