Ak v PHP napíšete a spustíte nasledujúci kód, čo nám vypíše?
$a = array( 'key' => 'value' ); // Nastavíme pole $a $b = $a; // Skopírujeme pole $a do poľa $b $a['key'] = 'value2'; // Zmeníme hodnotu 'key' v $a echo $b['key']; // Vypíšeme hodnotu 'key' v $b
Ak hádate ‘value’, tak hádate správne. Ako však dosiahnuť, aby jedna premenná odkazovala na druhú, bola iba akousi ikonou? V PHP to nie je náročné, stačí pri priradzovaní (2. riadok) dať po rovná sa (=) znak ampersandu (&):
$a = array( 'key' => 'value' ); $b = &$a; // $b odkazuje na rovnaké miesto kde $a $a['key'] = 'value2'; echo $b['key']; // value2
Ako to funguje na pozadí, vysvetlím o chvíľu, mám totiž ďalšiu otázku, čo sa stane v JavaScripte, keď napíšete nasledujúci kód?
a = { key: "value" }; b = a; a.key = "value2"; console.log( b.key ); // value2
Na konzolu sa vypíše ‘value2′. Dôvod, prečo sa to deje, ako nadpis prezrádza, je, že JavaScript nekopíruje, JavaScript ukazuje. Nie vždy. Pokým čísla, reťazce a bool budú vždy skopírované, polia a objekty budú odkazované.
Prečo sa to deje? Ako to skopírovať?
Myslím si, že túto vlastnosť zdedil JavaScript po jazyku C/C++. Však nakoniec práve do C++ sa JavaScript kompiluje (platí pre V8). V jazyku C/C++ si vytvoríte premennú (nazvime ju a), ktorá je automaticky ukazovateľom, následne (nie nutne v rovnakom riadku) potrebuje vyhradiť kus pamäte na ktorú bude novo vytvorená premenná odkazovať. Premenná teda odkazuje na kus pamäte. Keď vytvoríte druhú premennú (b) a priradíte ju ku prvej ( b = a), tak sa skopíruje adresa na ktorú ukazuje a obe budú ukazovať na rovnaké miesto. A to je presne správanie, ktoré vidíme v JavaScripte, ale chválabohu nám aspoň odpadla tá práca s pamäťou. Lenže, ako dáta skopírujeme?
Ak to budete niekedy hľadať, nazýva sa to “deep copy”, ale ľudia radi hovoria o klonovaní. jQuery má nato funkciu $.extend, ale v súčastnosti sa ako najefektívnejší spôsob ukazuje zkonvertovať to do JSON a späť:
a = { key: "value" }; b = JSON.parse( JSON.stringify( a )); a.key = "value2"; console.log( b.key ); // value
Funguje to pre objekty aj polia. Azda jediný problém by to teoreticky mohlo mať s cyklickými štruktúrami, ale aj to sa dá čiastočne riešiť.
Kedy to nefunguje?
Niekedy sa stáva, že sa ponáhľate a všetko vyzerá veľmi nádejne. Avšak práve vtedy sa niečo pokazí. V tomto prípade to pravdepodobne bude súvisieť s nasledujúcim kódom:
a = { key: "value" }; b = a; c = b; a = { another_key: "value2" } // Nastavíme a na inú hodnotu console.log(a); // { another_key: "value2"} console.log(b); // { key: value } console.log(c); // { key: value }
Či už sa pokúsite a zresetovať ( a = {};
), alebo priradiť úplne inú hodnotu ( a = other_value;
), ocitnete sa v rovnakej situácií. Popíšme si prečo.
Na začiatku priradíme do a
hodnotu, následne vytvoríme b
, ktoré bude ukazovať na a
, a potom vytvoríme c
, ktoré bude ukazovať na b
. Nie tak úplne. To, čo sa skutočne stane je, že b
a c
budú odkazovať na rovnakú pamäť ako a
, nie na premennú a
. Následne ale, vytvoríme novú pamäť s inou adresou (adresa je označenie nejakej konkrétnej časti pamäte), ktorú priradíme do a
. A keďže b
a c
, neodkazujú na premennú a
ale na tú adresu, na ktorú kedysi odkazovalo a
, ich hodnota sa nezmení. Je to komplikované, preto radšej prikladám obrázok: