Přejít k navigační liště

Zdroják » JavaScript » Kopírování/klonování objektů v JS

Kopírování/klonování objektů v JS

Články JavaScript

Jak správně a úspěšně zkopírovat objekt v JavaScriptu. Popíšeme různé metody a rozdíly mezi nimi.

Text vyšel původně na webu autora.

V JS dochází při kopírování objektů k trochu odlišné situaci než při kopírování obsahu proměnných.

Je-li cílem zkopírovat obsah proměnné prvni do proměnné druhy, jde to provést následovně (živá ukázka):

var prvni = 'hodnota'
var druhy = prvni

druhy = 'jinaHodnota'

console.log(prvni) // hodnota
console.log(druhy) // jinaHodnota

Stejným způsob způsobem se může nabízet zkopírovat/naklonovat objekt.

var prvni = {
    vlastnost: 'hodnota'
}

var druhy = prvni

console.log(prvni) // {"vlastnost":"hodnota"}
console.log(druhy) // {"vlastnost":"hodnota"}

Na první pohled to vypadá funkčně. Problém ale nastává, když se změní nějaká vlastnost zkopírovaného objektu (živá ukázka):

var prvni = {
    vlastnost: 'hodnota'
}

var druhy = prvni

druhy.vlastnost = 'jinaHodnota'

console.log(prvni) // {"vlastnost":"jinaHodnota"}
console.log(druhy) // {"vlastnost":"jinaHodnota"}

Jak je vidět z výstupu JS konsole, oba objekty jsou stejné. Proč? Tímto způsobem se nekopíruje objekt, ale jen se na něj vytváří reference/odkaz. Nepochopení tohoto principu vede ke značným problémům.

...Spread operátor

Řešení je použít tzv. spread operátor – tři tečky bezprostředně před názvem proměnné/objektu (živá ukázka):

var prvni = {
    vlastnost: 'hodnota'
}

var druhy = { ...prvni }

druhy.vlastnost = 'jinaHodnota'

console.log(prvni) // {"vlastnost":"hodnota"}
console.log(druhy) // {"vlastnost":"jinaHodnota"}

Totéž jde zapsat i zkráceně jako:

var prvni = {
    vlastnost: 'hodnota'
}

var druhy = { ...prvni, ...{ vlastnost: 'jinaHodnota' } }

Tedy při přiřazení rovnou změnit nějakou vlastnost kopírovaného objektu.

Spread operátor (v překladu z angličtiny něco jako rozložit/rozprostřít) byl standardizován až v roce 2018 v ECMAScriptu 2018 (zkráceně označovaném jako ES2018 nebo ES9). Nefunguje tedy ve starších prohlížečích.

Nejen z tohoto důvodu je občas možné vidět věci jako:

var druhy = JSON.parse(JSON.stringify(prvni))

Funguje to trochu jinak než třítečkový operátor, ale v tomto případě to účel plní stejně. Živá ukázka

Vzhledem k tomu, že to nejprve převádí objekt na řetězec a následně parsuje zpět na objekt, není to výkonově úplně nejlepší. Spíš nouzové řešení.

Další způsob je použít Object.assign. Ten byl standardizován dříve než ...spread operátor, takže v případě psaní JS kódu, který se už nekompiluje nástrojem typu Babel, může dávat větší smysl používat toto řešení (živá ukázka):

var druhy = Object.assign({}, prvni)

Hluboké a mělké klonování

Při klonování objektů je třeba rozlišovat tzv. hluboké klonování (deep clone) a mělké (shallow clone).

Spread operátor ... dělá právě to mělké.

To se projeví tak, že změna hodnoty o úroveň níž (v příkladu dalsiVlastnost) se projeví i u klonovaného objektu (živá ukázka):

var prvni = {
    vlastnost: 'hodnota',
    dalsiVlastnost: {
        text: 'ahoj'
    }
}

var druhy = { ...prvni }
druhy.vlastnost = 'jinaHodnota'
druhy.dalsiVlastnost.text = 'fytopuf'

console.log(prvni) // {"vlastnost":"hodnota","dalsiVlastnost":{"text":"fytopuf"}}
console.log(druhy) // {"vlastnost":"jinaHodnota","dalsiVlastnost":{"text":"fytopuf"}}

 

Tedy první úroveň je naklonovaná, ale hlouběji už je jen reference na původní objekt.

Mělkou kopii vytváří i konstrukce Object.assign.

Co s tím?

Asi nejjednodušší řešení bez používání cizích knihoven je již výše zmíněný JSON.parse(JSON.stringify(objekt)) (živá ukázka).

Není to ale z výše popsaných důvodů úplně čisté řešení. Další problém je v tom, že se hodí jen pro klonování primitivních datových typů jako je řetězec (String), číslo (Number) nebo true/false (Boolean).

Pokud bude v objektu třeba funkce, tento převod tam a zase zpět nepřežije.

Knihovny pro deep copy

Jako best-practice považuji použít např. funkci cloneDeep z populární knihovny Lodash (živá ukázka):

var prvni = {
    vlastnost: 'hodnota',
    dalsiVlastnost: {
        text: 'ahoj'
    }
}

var druhy = _.cloneDeepWith(prvni)

druhy.vlastnost = 'jinaHodnota'
druhy.dalsiVlastnost.text = 'fytopuf'

console.log(prvni) // {"vlastnost":"hodnota","dalsiVlastnost":{"text":"ahoj"}}
console.log(druhy) // {"vlastnost":"jinaHodnota","dalsiVlastnost":{"text":"fytopuf"}}

I kdysi populární knihovna jQuery má funkci extend, která umí hloubkově klonovat objekty (živá ukázka):

var druhy = $.extend(true, {}, prvni);

Odkazy jinam

Komentáře

Subscribe
Upozornit na
guest
11 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Jakub Vrána

Druhý spread u ukázky zkráceného zápisu je zbytečný:

var druhy = { ...prvni, ...{ vlastnost: 'jinaHodnota' } }

Lze to napsat takto:

var druhy = { ...prvni, vlastnost: 'jinaHodnota' }
Vítězslav Gazda

Fuj, strašný jazyk … === atd.

Mlocik97

JavaScript je v pohode ako každý iný jazyk, samozrejme má aj on svoje muchy a nechutnosti, ale tak ukáž mi jazyk, ktorý nemá.

L.

Co je na === fuj? Operátor jako každý jiný, to je v pohodě. Horší jsou třeba jazyky, kde neexistují blokové závorky a tak se bloky určují indentací, to je teprve správný humus.

dor

Je pravda, že samotný operátor === není fuj. Fuj jsou důvody, proč musí existovat. Protože == je v js fuj. Žít se s tím sice dá, ale já to fuj chápu.

Mlocik97

@dor a jeden fuj operátor znamená že celý JavaScript je strašný? Tak to potom je strašný absolútne každý programovací jazyk.

dor

To určitě ne. Jak jsem psal: fuj chápu, ale žít se s tím dá. Nemůžou být všechny jazyky tak skvělé, jako C#. :-D

Mlocik97

„tak skvelé ako …“ je typický názor fanatika, a nemá nič s kvalitou programovacieho jazyka.

dor

Zkus to přečíst ještě jednou a až budeš číst poslední tři znaky, polož hlavu na levé rameno. A hlavně zhluboka dýchej. Jsou to jenom nástroje. ;)

Mlocik97

Sry, ale keď napíšeš smajlík za každú vetu a pak sa na to odvolávaš, tak to není irónia, sarkazmus ani nič podobné ale len psychická poruha, a pripomína mi to typický postoj Rikiho Fridricha čo si z toho robil srandu a naprogramoval Mimi Tourette https://www.youtube.com/watch?v=aTucFxlZmTA

Martin Hassman

Jak to tu pár dní sleduji, vypadá to, že téma diskuse již bylo vyčerpáno a měla být už dávno ukončena. Tak ji dnes uzavírám.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.