Pasjonuję się IT, ekonomią i muzyką. Moim ulubionym językiem jest Java i w niej pracuję na codzień.
Kopiowanie to mechanizm tworzenia duplikatów. W przypadku prymitywów (string, number, boolean itd) sprawa jest prosta, możemy spokojnie wykonywać wszystkie operacje bez obaw o nadpisywanie oryginału.
Sprawa się komplikuje przy zastosowaniu przypisania obiektu za pomocą prostego = - wtedy tworzy się referencja do oryginału. Kopia jest zależna od oryginału, więc każda modyfikacja w obiekcie oddziałuje na resztę obiektów. Czasami taki efekt nie jest pożądany, niestety w js brakuje natywnych, prostych sposobów na klonowanie obiektów bez tworzenia referencji (wskaźnika na oryginał), trzeba w tym celu wykorzystywać różne hacki.
W tym artykule omówię część z nich, mam nadzieję że komuś się przydadzą.
Istnieją rodzaje kopiowania obiektów w js:
Zacznijmy od tego, w jaki sposób nie skopiujemy obiektu, a który jednak początkowo wydaje się słusznym i prostym rozwiązaniem. Jak widać, przy podmianie wartości w klonie, wartość oryginału również się zmieniła, co nie jest oczekiwanym efektem.
// proste przypisanie
var strawberry = {
color: 'red',
taste: 'sweet',
size: 'large',
shape: {
x: 10,
y: 32,
z: 33
}
};
var someOtherStrawberry = strawberry;
someOtherStrawberry.size = 'small'
console.log(strawberry.size);
// output: small
Część funkcji dostarczonych natywnie przez JavaScript umożliwia płytkie skopiowanie obiektu. Dopóki zajmujemy się przestrzenią bez zagnieżdżeń, wszystko działa jak powinno.
Problem pojawia się, gdy modyfikujemy zagnieżdżone obiekty - wtedy nadal możemy modyfikować oryginał, co nie jest pożądane (dzięki rekurencyjnemu użyciu płytkich kopii możemy jednak stworzyć głęboką kopię, czym zajmiemy się później).
Poniżej sposób na płytką kopię:
//Tworzy płytką kopię - klonuje właściwości najwyższego poziomu, pozostawiając referencję do zagnieżdzonych
var someOtherStrawberry = Object.assign({}, strawberry);
someOtherStrawberry.size = 'small';
console.log(strawberry.size)
// output: large
someOtherStrawberry.shape.x = 32
console.log(strawberry.shape.x)
// output: 32, wartość strawberry jest podmieniona przez modyfikacje na someOtherStrawberry!
Standard ES6 przynosi nam proste ułatwienia, pozwalające na dokonywanie kopii. Jednym z nich jest spread operator ...
, który płytko ignoruje wskaźnik do obiektu macierzystego. Dzięki temu otrzymujemy obiekty niezależne.
var strawberry = {
color: 'red',
taste: 'sweet',
size: 'large',
shape: {
x: 10,
y: 32,
z: 33
}
};
const someOtherStrawberry = {
...strawberry
};
someOtherStrawberry.size = 'small';
console.log(strawberry.size)
// output: large, wszystko OK!
someOtherStrawberry.shape.x = 32;
console.log(strawberry.shape.x)
// output: 32, znowu podmienione przez modyfikacje na someOtherStrawberry :(
Kiedy tworzymy klona, chcielibyśmy by był całkowicie niezależny referencyjnie od oryginału, a jednocześnie był identyczny jak on. Łatwo można to uzyskać za pomocą niezbyt eleganckiej sztuczki z JSONem. Metoda ta działa tylko z strukturami danych - obiekt w tym wypadku nie może zawierać funkcji.
Osobiście nie stosowałabym tego sposobu - w przypadku gdy przy wstępnych założeniach obiekt jest zwykłą strukturą danych, a później nieświadomy inny programista dopisze do niego metody, pojawi się nieoczekiwany i problematyczny bug.
var strawberry = {
color: 'red',
taste: 'sweet',
size: 'large',
shape: {
x: 10,
y: 32,
z: 33
}
};
const someOtherStrawberry = JSON.parse(JSON.stringify(strawberry));
someOtherStrawberry.size = 'small';
console.log(strawberry.size)
// output: large, wszystko OK!
someOtherStrawberry.shape.x = 32;
console.log(strawberry.shape.x)
// output: 10, wszystko OK!
Posiadamy już narzędzia do tworzenia płytkich kopii, możemy więc je wykorzystać przy tworzeniu głębokich kopii, wystarczy rekurencyjnie przejść po wszystkich węzłach.
Algorytm sprawdza czy przekazana w argumencie wartość - jeśli jest prymitywem, po prostu zwraca go. W przypadku, gdy argument jest obiektem, przechodzimy po każdym jego polu i sprawdzamy czy jest ono typem prymitywnym, jeśli nie - wywołujemy funkcję "rozbijającą" dla pola nie-prymitywnego .
Wszystko się powtarza do momentu, gdy algorytm przejdzie po całym drzewie.
const deepCopyFunc = child => {
let parent, value, key
if (typeof child !== "object" || child === null) {
return child // zwracamy jeśli jest typem prymitywnym
}
parent = Array.isArray(child) ? [] : {}
//jeśli jest obiektem to iterujemy po każdym property
for (key in child) {
value = child[key]
//jeśli wartość dla key jest obiektem to wykonujemy rekurencyjną kopie
if (value !== null && typeof value === "object") {
parent[key] = deepCopyFunc(value)
} else {
parent[key] = value
}
}
return parent
}
let originalArray = [37, 3700, {
hello: "world"
}]
Myślę, że artykuł okazał się pomocny, a moje objaśnienia niezbyt trudne. Jeśli zauważysz gdzieś błąd, proszę, napisz do mnie. Zależy mi, by mój blog był rzetelnym źródłem informacji, więc z chęcią przyjmuję krytykę.