JavaScript a ukazovatele

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:

javascript_pointers

Pridaj komentár

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>