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

Publikováno: 4.1.2021

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

Celý článek

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

Nahoru
Tento web používá k poskytování služeb a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tímto souhlasíte. Další informace