Referencja i kopiowanie obiektów

Kopiowanie obiektów

Tak jak już wspominaliśmy sobie w dziale o typach danych, typy złożone (obiekty) charakteryzują się tym, że jeżeli pod dwie i więcej zmiennych podstawimy ten sam obiekt, będą one wskazywać na ten sam byt:


const a = { name : "kot" }
const b = a;

b.age = 5;

console.log(a, b); //{name: "kot", age: 5}, {name: "kot", age: 5}

Jeżeli byśmy chcieli dodawać nowe właściwości tylko do zmiennej b, powinna ona zawierać kopię obiektu a. Taki duplikat możemy zrobić na kilka sposobów.

Zanim przejdziemy dalej, rozważmy przykład:


//mamy tablicę która chcemy posortować
const tab = [4, 10, 2, 11, 1.1, 3];

//piszemy odpowiednią funkcję
const sortNumbers = function(arr) {
    return arr.sort((a, b) => a - b);
}

const tabSorted = sortNumbers(tab);
console.log(tabSorted); //[1.1, 2, 3, 4, 10, 11]
console.log(tab); //[1.1, 2, 3, 4, 10, 11]

Nasza funkcja powinna zwrócić posortowaną tablicę. I robi to. Problem w tym, że dodatkowo modyfikowana jest oryginalna tablica.
Czemu tak się dzieje? Pamiętaj, że jeżeli przekazujesz do funkcji pod dany parametr jakiś obiekt, we wnętrzu tej funkcji występuje on pod nazwą parametru. Nie sprawia to jednak, że magicznie jest to kopia przekazanych danych - to wciąż ten sam obiekt. Można to przyrównać do sytuacji z pierwszego listingu. Dany obiekt (w tym przypadku tablica) po przekazaniu go jako argument występuje pod dwoma nazwami tab i arr. Funkcja sort modyfikuje tablicę, dlatego obie zmienne zostały zmienione.

I teraz wyobraź sobie, że piszesz jakąś dużą aplikację, gdzie na początku pobierasz tablicę użytkowników z serwera. Tablica taka ma konkretną kolejność, która z jakiegoś powodu będzie dla ciebie później ważna. Napisałeś kilka funkcji, które wykonują na tej tablicy różne czynności. Gdyby każda z nich modyfikowała początkową tablicę, bardzo szybko byś się zagubił.

Dlatego też dobrym pomysłem jest pisać funkcje tak, by zwracały nowe dane utworzone na bazie tych które im przekazano, ale też nie by bezpośrednio nie modyfikowały tych przekazanych. Czyli w skrócie - by działały na kopiach danych.

W najnowszym Javascripcie do kopiowania płaskich obiektów możemy wykorzystać spread syntax:


const a = {
    name : "kot",
    age: 3
}

const b = { ...a }
b.name = "pies"

console.log(a, b) {name: "kot", age: 3}, {name: "pies", age: 3}

const a = {
    name : "kot",
    speed: 10
}
const b = {
    name : "pies",
    age : 5
}

const c = { ...a, ...b }
console.log(c); //{name : "pies", speed: 10, age: 5}

Możemy też zastosować funkcję Object.assign(cel, ...źródła).

Funkcja ta służy do kopiowania wszystkich wyliczalnych właściwości z jednego lub więcej obiektów do obiektu docelowego.


const a = {
    name : "kot",
    age: 7,
    speed: 10
}

const b = {
    name : "pies",
    age : 5
}

const c = Object.assign({}, a, b);

console.log(c); //{name : "pies", age: 5, speed: 10}

const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };
const o3 = { c: 3 };

const obj = Object.assign({}, o1, o2, o3);
console.log(obj); //{ a: 1, b: 2, c: 3 }

Powyższymi metodami (spread i assign) możemy skopiować tylko płaskie obiekty. Jeżeli któraś z właściwości wskazuje na inny obiekt, zostanie skopiowana tylko referencja do tego obiektu:


const food = { type : "rybki" }

const a = {
    name : "kot",
    food : food
}

const b = { ...a }
b.food.type = "chrupki";

console.log(b.food, a.food); //{type : "chrupki"}, {type : "chrupki"}

Żeby sklonować obiekt głęboko, musimy zastosować inne techniki - np. skorzystać z obiektu JSON


const ob = {
    name : "Marcin",
    pet : {
        name : "Feliks",
        kind : "cat"
    }
}

const ob2 = JSON.parse(JSON.stringify(ob));

ob2.pet.name = "Super Szamson";
ob2.pet.kind = "pies";

console.log(ob.pet.name, ob2.pet.name); //Feliks, Super Szamson
console.log(ob.pet.kind, ob2.pet.kind); //cat, pies

Ale nawet to rozwiązanie nie jest w 100% bezpieczne, ponieważ nie sklonuje nam obiektów z metodami

Jeżeli zdarzy się sytuacja, że będziesz potrzebował kopiować naprawdę skomplikowane obiekty, wtedy warto zainteresować się rozwiązaniami takimi jak https://lodash.com/docs/4.17.4#cloneDeep

Ciekawy artykuł na temat kopiowania obiektów znajduje się pod adresem https://dassur.ma/things/deep-copy/.

Przy czym tak jak wspomniałem powyżej - takie sytuacje zdarzają się bardzo rzadko i najczęściej nie ma potrzeby wykonywać żadnych kopi.

Dodatkowe materiały

W Internecie jest wiele fajnych materiałów na poniższe tematy. Możliwe, że ten film lepiej wyjaśni ci powyższe zagadnienia.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.

Menu