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.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.