Kopírování objektů v JS

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ě:

var prvni = 'hodnota'
var druhy = prvni

druhy = 'jinaHodnota'

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

Živá ukázka

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:

var prvni = {
    vlastnost: 'hodnota'
}

var druhy = prvni

druhy.vlastnost = 'jinaHodnota'

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

Živá ukázka

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:

var prvni = {
    vlastnost: 'hodnota'
}

var druhy = { ...prvni }

druhy.vlastnost = 'jinaHodnota'

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

Živá ukázka

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 standardisová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 standardisová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í.

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

Živá ukázka

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.

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"}}

Živá ukázka

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:

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"}}

Živá ukázka

I v minulosti populární knihovna jQuery má funkci extend, která umí hloubkově klonovat objekty:

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

Živá ukázka

Odkazy jinam

To je všechno. Líbil se vám článek a chcete se dozvědět, až vyjde další?

Sledujte:

 

Připomínky mi pište do komentářů ↓

Jak vytvořit WYSIWYG editor

Vlastní jednoduchý WYSIWYG editor

Chceme-li na webu zadávat text a běžná <textarea> už nestačí, řešením je napsat si vlastní WYSIWYG editor.

Detekce zapnutého JavaScriptu

Zapnutý a vypnutý JavaScript

Jak na webové stránce detekovat zapnuté nebo vypnuté skriptování.

CSS vyhledávání a filtrování

CSS vyhledávání a filtrování obsahu

Jak pomocí CSS se špetkou JS filtrovat obsah stránky nebo na ní vyhledávat.

Upozornění před opuštěním stránky

Upozornění před zavřením stránky

Javascriptová událost onbeforeunload umožňuje pozastavit uzavření/obnovení stránky.

Plynulý přesun focusu

Plynulý přesun focusu

Plynulé přesouvání focusu mezi jednotlivými položkami formuláře.

Komentáře