Dziś chciałabym zwrócić uwagę na dwa rodzaje typów danych w JS: prymitywne oraz referencyjne. Może wydaje się to nudną teorią, ale warto spojrzeć na prosty przykład obrazujący najistotniejszą różnicę między owymi typami. Załóżmy, że posiadamy zmienną A równą ileśtam oraz zmienną B, której przypisujemy zmienną A.
Dane o typie prymitywnym
(np. liczby, dane tekstowe…)
Wartością zmiennej jest jej faktyczna wartość (zajmująca raczej mało miejsca w pamięci). Przekazując jedną zmienną do drugiej, tworzymy KOPIĘ pierwszej zmiennej.
Dzięki temu przy zmianie tej drugiej zmiennej, pierwsza zostanie niezmieniona.
var varA = 5;
var varB = varA; // do zmiennej varB przypisujemy wartość varA, czyli 5
varA = 10;
console.log(varA); // 10
console.log(varB); // 5
Dane o typie referencyjnym
(np. obiekt, tablica…)
Wartością zmiennej jest referencja do adresu miejsca w pamięci, w którym z kolei znajdują się faktyczne dane (raczej duże). Przekazując jedną zmienną do drugiej, podajemy REFERENCJĘ do tego samego obiektu.
Skutkuje to tym, że zmieniając drugą zmienną – zmieniamy też pierwszą. Oczywiście to zachowanie zazwyczaj niepożądane.
/*** OBIEKTY ***/
var arr1 = [1, 2, 3];
var arr2 = arr1; //zmienna arr2 wskazuje na tablicę [1, 2, 3]
arr1.push(4);
console.log(arr1 === arr2); //true
console.log(arr1); //1, 2, 3, 4
console.log(arr2); //1, 2, 3, 4
/*** TABLICE ***/
const player = {
name: "Neymar",
team: "FCB",
age: 24
}
const newPlayer = player;
newPlayer.name = "Ronaldo";
newPlayer.team = "Real Madryt";
newPlayer.age = 31;
console.log(newPlayer === player) // true
console.log(player) // { name: "Ronaldo", team: "Real Madryt", age: 31 }
console.log(newPlayer) // { name: "Ronaldo", team: "Real Madryt", age: 31 }
Słowo o niezmienności danych
Niezmienność danych jest ważna. Każda zmiana na danych jest błędogenna.
Bardzo łatwo pisać „niezmienny” kod dla danych prymitywnych, ponieważ one z definicji nie mogą się zmieniać. Zmienne prymitywne zawsze wskazują aktualną wartość. Nawet podczas przekazania zmiennej do drugiej zmiennej, ta druga dostaje tylko kopię wartości oryginalnej zmiennej.
Problem pojawia się przy obiektach, kiedy przekazujemy drugiemu obiektowi referencję do istotnie oryginalnego obiektu. Nanosząc zmiany na drugim obiekcie, jednocześnie zmieniamy pierwszy.
Sposoby na niezmienność danych referencyjnych w ES6
Dla obiektów sprawa jest prosta – zamiast przekazywać jeden obiekt drugiemu, stwórzmy po prostu zupełnie nowy obiekt. Logiczne, co nie?
const player = {
name: "Neymar",
team: "FCB",
age: 24
}
const newPlayer = Object.assign({}, player, {
team: "Real Madryt"
})
console.log(newPlayer === player) // false
console.log(player) // { name: "Neymar", team: "FCB", age: 24 }
console.log(newPlayer) // { name: "Neymar", team: "Real Madryt", age: 24 }
Object.assign
to nowość w ES6. Jako parametry przyjmuje różne obiekty, a następnie łączy je z pierwszym podanym obiektem. My jako pierwszy parametr podaliśmy pusty obiekt. Dzięki temu nie nadpisujemy żadnego obiektu, a tworzymy świeży twór zachowując zasadę niezmienności danych. Tym oto sposobem zmieniliśmy Neymarowi drużynę.
Drugim rozwiązaniem problemu dostępnym tylko dla ES6 jest użycie operatora spread
.
const player = {
name: 'Neymar',
team: 'FCB',
age: 24
}
const newPlayer = {
...player,
age: 31
}
console.log(newPlayer === player) // false
console.log(player) // { name: "Neymar", team: "FCB", age: 24 }
console.log(newPlayer) // { name: "Neymar", team: "FCB", age: 31 }
Tym razem nasz spread
operator kopiuje wszystkie właściwości obiektu 'player’ do nowego obiektu 'newPlayer’, a następnie nadpisuje je. W naszym przypadku dodajemy kilka lat Neymarowi. Uwaga! Kolejność jest tu istotna – to dane z 'player’ nadpisujemy nowym wiekiem. Gdybyśmy zamienili te 2 linijki kodu – wiek 31 nadpisalibyśmy wiekiem 24.
Operator spread
działa również na tablicach.
var arr1 = [1, 2, 3];
var arr2 = [...arr1, 4];
console.log(arr1 === arr2); // false
console.log(arr1); // 1, 2, 3
console.log(arr2); // 1, 2, 3, 4
Istnieje również szereg innych metod działających na tablicach, które wypluwają nową zmienną, nie naruszając oryginalnej, np: map, filter, concat, slice.
Niestety trzeba uważać na metody push, splice oraz sort, które zmieniają oryginalną tablicę.
P.S. O różnicach między slice a splice przeczytasz również TUTAJ.