Jeden ze základnÃch rozdÃlů mezi objekty a primitivnÃmi typy je, že objekty se ukládajà a kopÃrujà jako âodkazyâ, zatÃmco primitivnà hodnoty: ÅetÄzce, ÄÃsla, booleany atd. â se kopÃrujà vždy âjako celá hodnotaâ.
Snadno tomu porozumÃme, jestliže se podÃváme trochu vÃce pod kapuci toho, co se dÄje, když kopÃrujeme hodnotu.
ZaÄnÄme primitivnÃm typem, tÅeba ÅetÄzcem.
Do promÄnné vÄta zkopÃrujeme promÄnnou zpráva:
let zpráva = "Ahoj!";
let vÄta = zpráva;
Výsledkem budou dvÄ nezávislé promÄnné, v každé bude uložen ÅetÄzec "Ahoj!".
Vcelku zjevný výsledek, že?
Objekty se takhle nechovajÃ.
V promÄnné, do nÞ je pÅiÅazen objekt, nenà uložen samotný objekt, ale jeho âadresa v pamÄtiâ â jinými slovy âodkazâ na objekt.
PodÃvejme se na pÅÃklad takové promÄnné:
let uživatel = {
jméno: "Jan"
};
A takto je ve skuteÄnosti uložen v pamÄti:
Objekt je uložen nÄkde v pamÄti (na obrázku vpravo), zatÃmco promÄnná uživatel (vlevo) obsahuje âodkazâ na nÄj.
Můžeme se dÃvat na objektovou promÄnnou, napÅ. uživatel, jako na kus papÃru, na nÄmž je napsána adresa objektu.
Když provádÃme akci nad objektem, napÅ. zjišťujeme vlastnost uživatel.jméno, motor JavaScriptu se podÃvá na to, co je na této adrese, a provede operaci nad skuteÄným objektem.
TeÄ vysvÄtlÃme, proÄ je to důležité.
Když je kopÃrována objektová promÄnná, zkopÃruje se odkaz, ale samotný objekt se nezdvojÃ.
NapÅÃklad:
let uživatel = { jméno: "Jan" };
let správce = uživatel; // kopÃrovánà odkazu
Nynà máme dvÄ promÄnné, v obou jsou uloženy odkazy na tentýž objekt:
Jak vidÃte, objekt je stále jen jeden, ale nynà se na nÄj odkazujà dvÄ promÄnné.
ObÄ promÄnné můžeme použÃvat k pÅÃstupu k objektu a modifikaci jeho obsahu:
let uživatel = { jméno: 'Jan' };
let správce = uživatel;
správce.jméno = 'Petr'; // zmÄna pomocà odkazu âsprávceâ
alert(uživatel.jméno); // 'Petr', zmÄny jsou vidÄt i z odkazu âuživatelâ
Je to, jako kdybychom mÄli skÅÃÅ se dvÄma klÃÄi a použili jeden z nich (správce) k tomu, abychom se do nà dostali a provedli zmÄny. Když poté použijeme druhý klÃÄ (uživatel), budeme stále otevÃrat stejnou skÅÃÅ a můžeme pÅistupovat ke zmÄnÄnému obsahu.
Porovnánà pomocà odkazů
Dva objekty jsou si rovny, jen když je to jeden a tentýž objekt.
NapÅÃklad zde a a b jsou odkazy na tentýž objekt, takže jsou si rovny:
let a = {};
let b = a; // kopÃrovánà odkazu
alert( a == b ); // true, obÄ promÄnné se odkazujà na tentýž objekt
alert( a === b ); // true
A zde si dva nezávislé objekty nejsou rovny, pÅestože vypadajà podobnÄ (oba jsou prázdné):
let a = {};
let b = {}; // dva nezávislé objekty
alert( a == b ); // false
PÅi porovnánà typu obj1 > obj2 nebo pÅi porovnánà s primitivnÃm typem obj == 5 se objekty pÅevádÄjà na primitivy. Jak funguje porovnávánà objektů, prostudujeme velmi brzy, ale upÅÃmnÄ ÅeÄeno, taková porovnánà jsou zapotÅebà jen velmi zÅÃdka â obvykle se objevujà v důsledku programátorské chyby.
Důležitým vedlejÅ¡Ãm efektem ukládánà objektů jako odkazů je, že objekt deklarovaný jako const může být modifikován.
NapÅÃklad:
const uživatel = {
jméno: "Jan"
};
uživatel.jméno = "Petr"; // (*)
alert(uživatel.jméno); // Petr
Může se zdát, že na Åádku (*) bude ohlášena chyba, ale nestane se tak. Hodnota objektu uživatel je konstantnà a musà poÅád odkazovat na stejný objekt, ale vlastnosti tohoto objektu lze libovolnÄ mÄnit.
Jinými slovy, const uživatel vyvolá chybu, jen pokud se pokusÃme nastavit uživatel=... jako celek.
Jestliže ovÅ¡em skuteÄnÄ potÅebujeme uÄinit vlastnosti objektů konstantnÃ, je to rovnÄž možné, ale úplnÄ jiným způsobem. ZmÃnÃme se o tom v kapitole PÅepÃnaÄe a deskriptory vlastnostÃ.
Klonovánà a sluÄovánÃ, Object.assign
KopÃrovánà objektové promÄnné tedy vytvoÅà dalšà odkaz na tentýž objekt.
Co když vÅ¡ak potÅebujeme duplikovat objekt?
Můžeme vytvoÅit nový objekt a replikovat strukturu existujÃcÃho objektu tÃm, že budeme iterovat nad jeho vlastnostmi a kopÃrovat je na úrovni primitivů.
NapÅÃklad:
let uživatel = {
jméno: "Jan",
vÄk: 30
};
let klon = {}; // nový prázdný objekt
// zkopÃrujme do nÄj vÅ¡echny uživatelské vlastnosti
for (let klÃÄ in uživatel) {
klon[klÃÄ] = uživatel[klÃÄ];
}
// nynà je klon plnÄ nezávislý objekt se stejným obsahem
klon.jméno = "Petr"; // zmÄnÃme data uvnitÅ
alert( uživatel.jméno ); // v původnÃm objektu je stále Jan
Můžeme také použÃt metodu Object.assign.
Jejà syntaxe je:
Object.assign(cÃl, ...zdroje])
- Prvnà argument
cÃlje cÃlový objekt. - Dalšà argumenty jsou seznam zdrojů.
Metoda zkopÃruje vlastnosti vÅ¡ech zdrojových objektů do cÃle cÃl a ten pak vrátà jako svůj výsledek.
NapÅÃklad máme objekt uživatel. PÅidejme do nÄj nÄkolik oprávnÄnÃ:
let uživatel = { jméno: "Jan" };
let oprávnÄnÃ1 = { můžeProhlÞet: true };
let oprávnÄnÃ2 = { můžeEditovat: true };
// zkopÃruje vÅ¡echny vlastnosti z objektů oprávnÄnÃ1 a oprávnÄnÃ2 do objektu uživatel
Object.assign(uživatel, oprávnÄnÃ1, oprávnÄnÃ2);
// nynà uživatel = { jméno: "Jan", můžeProhlÞet: true, můžeEditovat: true }
alert(uživatel.jméno); // John
alert(uživatel.můžeProhlÞet); // true
alert(uživatel.můžeEditovat); // true
Jestliže vlastnost s kopÃrovaným názvem již existuje, bude pÅepsána:
let uživatel = { jméno: "Jan" };
Object.assign(uživatel, { jméno: "Petr" });
alert(uživatel.jméno); // nynà uživatel = { jméno: "Petr" }
Můžeme také využÃt Object.assign k provedenà jednoduchého klonovánà objektu:
let uživatel = {
jméno: "Jan",
vÄk: 30
};
let klon = Object.assign({}, uživatel);
alert(clone.name); // John
alert(clone.age); // 30
Zde metoda zkopÃruje vÅ¡echny vlastnosti objektu uživatel do prázdného objektu a ten pak vrátÃ.
Existujà i jiné metody klonovánà objektu, napÅ. použitÃm rozÅ¡ÃÅené syntaxe klon = {...uživatel}, kterou vysvÄtlÃme pozdÄji v tomto tutoriálu.
VnoÅené klonovánÃ
Až dosud jsme pÅedpokládali, že vÅ¡echny vlastnosti objektu uživatel jsou primitivnÃ. Ale vlastnosti mohou být i odkazy na jiné objekty.
NapÅÃklad:
let uživatel = {
jméno: "Jan",
mÃry: {
výška: 182,
Å¡ÃÅka: 50
}
};
alert( uživatel.mÃry.výška ); // 182
TeÄ nestaÄà kopÃrovat klon.mÃry = uživatel.mÃry, protože uživatel.mÃry je objekt a bude zkopÃrován odkazem, takže klon a uživatel budou sdÃlet stejné mÃry:
let uživatel = {
jméno: "Jan",
mÃry: {
výška: 182,
Å¡ÃÅka: 50
}
};
let klon = Object.assign({}, uživatel);
alert( uživatel.mÃry === klon.mÃry ); // true, stejný objekt
// uživatel a klon sdÃlejà tytéž mÃry
uživatel.mÃry.Å¡ÃÅka = 60; // zmÄnÃme vlastnost na jednom mÃstÄ
alert(klon.mÃry.Å¡ÃÅka); // 60, zÃskáme výsledek z jiného mÃsta
Abychom to opravili a uÄinili objekty uživatel a klon skuteÄnÄ oddÄlenými, mÄli bychom použÃt klonovacà cyklus, který prozkoumá každou hodnotu uživatel[klÃÄ], a pokud je to objekt, replikuje i jeho strukturu. Toto klonovánà se nazývá âhloubkovéâ (âhlubokéâ) anebo âstrukturovanéâ. Existuje metoda structuredClone, která implementuje hloubkové klonovánÃ.
structuredClone
Volánà structuredClone(objekt) vytvoÅà klon objektu objekt se vÅ¡emi vnoÅenými vlastnostmi.
V naÅ¡em pÅÃkladu ji můžeme použÃt následovnÄ:
let uživatel = {
jméno: "Jan",
mÃry: {
výška: 182,
Å¡ÃÅka: 50
}
};
let klon = structuredClone(uživatel);
alert( uživatel.mÃry === klon.mÃry ); // false, různé objekty
// uživatel a klon nynà nejsou nijak propojeny
uživatel.mÃry.Å¡ÃÅka = 60; // zmÄnÃme vlastnost na jednom mÃstÄ
alert(klon.mÃry.Å¡ÃÅka); // 50, nemÄlo to vliv
Metoda structuredClone může klonovat vÄtÅ¡inu datových typů, napÅ. objekty, pole, primitivnà hodnoty.
Podporuje i kruhové odkazy, kdy vlastnost nÄjakého objektu odkazuje na tento objekt samotný (pÅÃmo nebo skrz ÅetÄzec odkazů).
PÅÃklad:
let uživatel = {};
// vytvoÅme kruhový odkaz:
// uživatel.já odkazuje na samotného uživatele
uživatel.já = uživatel;
let klon = structuredClone(uživatel);
alert(klon.já === klon); // true
Jak vidÃte, klon.já odkazuje na klon, ne na uživatel! Kruhový odkaz byl tedy rovnÄž správnÄ naklonován.
Existujà vÅ¡ak pÅÃpady, kdy structuredClone selhává.
NapÅÃklad když objekt má funkÄnà vlastnost:
// chyba
structuredClone({
f: function() {}
});
FunkÄnà vlastnosti nejsou podporovány.
Abychom zvládli tak složité pÅÃklady, možná budeme muset použÃt kombinaci klonovacÃch metod, napsat si vlastnà kód nebo, abychom znovu nevynalézali kolo, použÃt existujÃcà implementaci, napÅÃklad _.cloneDeep(obj) z JavaScriptové knihovny lodash.
ShrnutÃ
Objekty se pÅiÅazujà a kopÃrujà odkazem. Jinými slovy, v promÄnné nenà uložena âhodnota objektuâ, ale âodkazâ (adresa v pamÄti) na tuto hodnotu. ZkopÃrovánà této hodnoty nebo jejà pÅedánà do funkce jako argument tedy zkopÃruje tento odkaz, ne objekt samotný.
VÅ¡echny operace na zkopÃrovaných odkazech (napÅ. pÅidávánà nebo odebÃránà vlastnostÃ) jsou provádÄny na jednom a tomtéž objektu.
Abychom vytvoÅili âskuteÄnou kopiiâ (klon), můžeme použÃt Object.assign pro tzv. âmÄlkou kopiiâ (vnoÅené objekty se kopÃrujà odkazem) nebo funkci structuredClone pro âhloubkové klonovánÃâ, nebo zákaznickou implementaci klonovánÃ, napÅ. _.cloneDeep(obj).
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)