Referencja

Opublikowane przez lewy w dniu

W JavaScript/TypeScript rozróżniamy typy proste i typy złożone. Typy proste to string, number, boolean. Zmienna przechowująca typ prosty przechowuje po prostu wartość. Jeżeli wartość jest typu złożonego (obiekt, tablica) to wówczas zmienna jest referencją do tej wartości a nie samą wartością.

Jeżeli utworzymy zmienną typu prostego a następnie drugą zmienną której jako wartość inicjującą przypiszemy wcześniej utworzoną zmienną, to wówczas druga zmienna otrzyma kopię wartości znajdującej się w pierwszej zmiennej.

var x = 1;
var y = x; // y = 1
x = 2;
console.log(x); // 2
console.log(y); // 1

Sprawa ma się trochę inaczej w przypadku typów złożonych. Jeżeli do zmiennej przypiszemy typ złożony (np. nowy obiekt) to zmienna będzie tak naprawdę referencją na ten obiekt a nie samym obiektem. Jeżeli utworzymy drugą zmienną i przypiszemy do niej wartość pierwszej zmiennej to tak naprawdę utworzymy drugą referencję do wcześniej stworzonego obiektu a nie utworzymy drugi obiekt.

var x = { value: 1 };
var y = x; // second reference to { value: 1 }
x.value = 2;
console.log(x.value); // 2
console.log(y.value); // 2 !!!

Ponieważ x i y są referencjami na ten sam obiekt, to nie ważne którą zmienna będziemy się posługiwać, edytować będziemy te same wartości.

Do tej pory wszystko powinno być intuicyjne i proste. Przejdźmy do porównywanie typów prostych i złożonych. Do porównywania typów prostych możemy używać === (=== jest zalecane ponieważ porównuje typy i wartości a nie same wartości, jak ==) i zwrócony wynik będzie taki jak się spodziewamy, czyli czy wartości zmiennych są takie same.

var x = 1;
var y = x;
x = 2;
console.log(x === y); // false
x = 1;
console.log(x === y); // true

var a = 1;
var b = 1;
console.log(a === b); // true

W przypadku referencji stosowanie znaków === może być mało intuicyjne.

var x = { value: 1 };
var y = x;
x.value = 2;
console.log(x === y); // true

i powodować pewne problemy

var x = { value: 1 };
var y = { value: 1 }; // reference to new object, not new reference to the same object
console.log(x === y); // false

Aby porównać czy wartości referencji są takie same, a nie czy referencja wskazuje na ten sam obiekt musimy sami napisać sobie do tego funkcję.

var x = { value: 1 };
var y = { value: 1 };
function compareMyObject(a, b) {
  return a.value === b.value;
}
console.log(compareMyObject(x, y));

Powyższy przykład funkcji jest bardzo uproszczony, ale obrazuje co chcemy osiągnąć, pierwszy problem to taki, że tak napisaną funkcję możemy stosować tylko do tych samych rodzajów obiektów (posiadających te same własności) i kształt tych obiektów musimy znać „z góry”. Kolejne problemy to np. długość takich funkcji gdy posiada dużo własności, warto pomyśleć o polu identyfikującym konkretny obiekt, np. uuid i porównując obiekt porównywać wartość uuid czy jest taka sama.

Innym sposobem na porównanie obiektów jest porównanie ich wartości json w postaci string.

var x = { value: 1 };
var y = { value: 1 };
function compareObject(a, b) {
  return JSON.stringify(a) === JSON.stringify(b);
}
console.log(compareObject(x, y));

Można też użyć funkcję isEqual z biblioteki lodash – https://lodash.com/docs/#isEqual

Ostatnia rzecz o której wspomnę to przekazywanie wartości do funkcji. W JavaScript/TypeScript wartości do funkcji przekazywane są przez wartość a nie przez referencję.

Dla typów prostych oczywiście jest to intuicyjne i proste.

function myFn(a) {
  a = 2;
  return a;
}
var x = 1;
console.log(myFn(x)); // 2
console.log(x); // 1

Do funkcji myFn przekazana została wartość zmiennej x i dostępna lokalnie w tej funkcji pod zmienną a. Wszystkie edycje na zmiennej lokalnej a nie wpływają w żaden sposób na zmienną x.

Dla typów złożonych nie jest to aż tak intuicyjne.

function myFn(a) {
  a.value = 2;
  return a;
}
var x = { value: 1 };
console.log(myFn(x).value); // 2
console.log(x.value); // 2 !!!

ale

function myFn(a) {
  a = { value: 2 };
  return a;
}
var x = { value: 1 };
console.log(myFn(x).value); // 2
console.log(x.value); // 1

Tak jak napisałem wcześniej do funkcji przekazywana jest wartość a nie referencja, czyli jeżeli wykonujemy funkcję której parametrem jest typ złożony to funkcja dostaje wartość naszej zmiennej (wartość na którą wskazuje referencja) i tworzy sobie lokalnie referencję do tej wartości. Czyli wewnątrz funkcji otrzymamy lokalną zmienną wskazującą na ten sam obiekt co zmienna po za funkcją, co skutkuje tym, że jeżeli edytujemy własności takiego obiektu to edytujemy obiekt na który wskazuje również zmienna (referencja) po za funkcją, natomiast jeżeli do zmiennej (referencji) wewnątrz funkcji przypiszemy nowy obiekt to zmienna lokalna będzie referencją na ten nowy obiekt, ale zmienna po za funkcją będzie nadal wskazywać na obiekt na który wskazywała wcześniej.


Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *