Ak v PHP napíšete a spustíte nasledujúci kód, čo nám vypíše?
1 2 3 4 | $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 (&):
1 2 3 4 | $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?
1 2 3 4 | 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äť:
1 2 3 4 | 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:
1 2 3 4 5 6 7 | 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: