Obiekty są podstawowymi blokami języka JavaScript. Obiekt jest kolekcją właściwości, a właściwość jest związkiem pomiędzy kluczem (lub nazwą) i wartością. Prawie wszystkie obiekty w JavaScript są instancjami Object
, który znajduje się na szczycie łańcucha prototypów.
Wprowadzenie
Jak wiadomo, operator przypisania nie tworzy kopii obiektu, a jedynie przypisuje do niego referencję, przyjrzyjmy się następującemu kodowi:
let obj = { a: 1, b: 2,};let copy = obj;obj.a = 5;console.log(copy.a);// Result // a = 5;
Zmienna obj
jest pojemnikiem na zainicjalizowany nowy obiekt. Zmienna copy
wskazuje na ten sam obiekt i jest odniesieniem do tego obiektu. Więc w zasadzie ten { a: 1, b: 2, }
obiekt mówi: Istnieją teraz dwa sposoby, aby uzyskać do mnie dostęp. Musisz przejść przez zmienną obj
lub zmienną copy
w każdym razie nadal się do mnie dostaniesz i wszystko co zrobisz ze mną poprzez te sposoby (bramy) będzie miało na mnie wpływ.
Immutowalność jest szeroko omawiana w tych dniach i musisz słuchać tego połączenia! Ta metoda usuwa jakąkolwiek formę niezmienności i może prowadzić do błędów, jeśli oryginalny obiekt zostanie użyty przez inną część twojego kodu.
Naiwny sposób kopiowania obiektów
Naiwny sposób kopiowania obiektów polega na zapętlaniu oryginalnego obiektu i kopiowaniu każdej właściwości po kolei. Przyjrzyjmy się temu kodowi:
function copy(mainObj) { let objCopy = {}; // objCopy will store a copy of the mainObj let key; for (key in mainObj) { objCopy = mainObj; // copies each property to the objCopy object } return objCopy;}const mainObj = { a: 2, b: 5, c: { x: 7, y: 4, },}console.log(copy(mainObj));
Inherent Issues
-
objCopy
Obiekt ma nowąObject.prototype
metodę różną od metodymainObj
prototypu obiektu, co nie jest tym, czego chcemy. Chcemy dokładnej kopii oryginalnego obiektu. - Deskryptory właściwości nie są kopiowane. Deskryptor „writable” z wartością ustawioną na false będzie miał wartość true w obiekcie
objCopy
. - Powyższy kod kopiuje tylko enumeratywne właściwości
mainObj
. - Jeśli jedna z właściwości w oryginalnym obiekcie jest obiektem samym w sobie, to będzie ona współdzielona pomiędzy kopią a oryginałem sprawiając, że ich odpowiednie właściwości będą wskazywać na ten sam obiekt.
Płytkie kopiowanie obiektów
Obiekt jest płytko skopiowany, gdy źródłowe właściwości najwyższego poziomu są skopiowane bez żadnego odniesienia i istnieje właściwość źródłowa, której wartość jest obiektem i jest skopiowana jako odniesienie. Jeśli wartość źródłowa jest referencją do obiektu, to kopiuje tylko tę wartość referencyjną do obiektu docelowego.
Płytka kopia zduplikuje właściwości najwyższego poziomu, ale zagnieżdżony obiekt jest współdzielony pomiędzy oryginałem (źródło) i kopią (cel).
Używanie metody Object.assign()
Metoda Object.assign() służy do kopiowania wartości wszystkich enumeratywnych własnych właściwości z jednego lub więcej obiektów źródłowych do obiektu docelowego.
let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy);// Result - { a: 1, b: 2 }
Dobrze, to jak na razie wykonuje zadanie. Stworzyliśmy kopię obj
. Zobaczmy, czy niezmienność istnieje:
let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy); // result - { a: 1, b: 2 }objCopy.b = 89;console.log(objCopy); // result - { a: 1, b: 89 }console.log(obj); // result - { a: 1, b: 2 }
W powyższym kodzie, zmieniliśmy wartość właściwości 'b'
w obiekcie objCopy
na 89
i kiedy logujemy zmodyfikowany obiekt objCopy
w konsoli, zmiany dotyczą tylko objCopy
. Ostatnia linia kodu sprawdza, czy obiekt obj
jest nadal nienaruszony i nie uległ zmianie. Oznacza to, że udało nam się utworzyć kopię obiektu źródłowego bez żadnych odniesień do niego.
Pitfall of Object.assign()
Nie tak szybko! Chociaż udało nam się stworzyć kopię i wszystko wydaje się działać dobrze, pamiętasz, że rozmawialiśmy o płytkim kopiowaniu? Spójrzmy na ten przykład:
let obj = { a: 1, b: { c: 2, },}let newObj = Object.assign({}, obj);console.log(newObj); // { a: 1, b: { c: 2} }obj.a = 10;console.log(obj); // { a: 10, b: { c: 2} }console.log(newObj); // { a: 1, b: { c: 2} }newObj.a = 20;console.log(obj); // { a: 10, b: { c: 2} }console.log(newObj); // { a: 20, b: { c: 2} }newObj.b.c = 30;console.log(obj); // { a: 10, b: { c: 30} }console.log(newObj); // { a: 20, b: { c: 30} }// Note: newObj.b.c = 30; Read why..
Dlaczego obj.b.c = 30?
Cóż, to jest pułapka Object.assign()
Object.assign
tworzy tylko płytkie kopie. Zarówno newObj.b
, jak i obj.b
współdzielą to samo odniesienie do obiektu, ponieważ indywidualne kopie nie zostały wykonane, zamiast tego skopiowano odniesienie do obiektu. Każda zmiana dokonana w dowolnej właściwości obiektu ma zastosowanie do wszystkich odniesień korzystających z obiektu. Jak możemy to naprawić? Czytaj dalej… mamy poprawkę w następnej sekcji.
Uwaga: Właściwości na łańcuchu prototypów i właściwości nieenumerowalne nie mogą być kopiowane. Zobacz tutaj:
let someObj = { a: 2,}let obj = Object.create(someObj, { b: { value: 2, }, c: { value: 3, enumerable: true, },});let objCopy = Object.assign({}, obj);console.log(objCopy); // { c: 3 }
-
someObj
jest na łańcuchu prototypu obj’a więc nie zostanie skopiowany. -
property b
jest właściwością niewyliczalną. -
property c
ma deskryptor właściwości enumeratywnej pozwalający na wyliczanie. Dlatego właśnie został skopiowany.
Głębokie kopiowanie obiektów
Głębokie kopiowanie zduplikuje każdy obiekt, który napotka. Kopia i oryginalny obiekt nie będą miały nic wspólnego, więc będzie to kopia oryginału. Oto rozwiązanie problemu, który napotkaliśmy używając Object.assign()
. Zbadajmy to.
Użycie JSON.parse(JSON.stringify(object));
To naprawia problem, który mieliśmy wcześniej. Teraz newObj.b
ma kopię, a nie referencję! Jest to sposób na głębokie kopiowanie obiektów. Oto przykład:
let obj = { a: 1, b: { c: 2, },}let newObj = JSON.parse(JSON.stringify(obj));obj.b.c = 20;console.log(obj); // { a: 1, b: { c: 20 } }console.log(newObj); // { a: 1, b: { c: 2 } } (New Object Intact!)
Immutable: ✓
Pitfall
Niestety, ta metoda nie może być użyta do kopiowania metod obiektów zdefiniowanych przez użytkownika. Zobacz poniżej.
Kopiowanie metod obiektu
Metoda jest właściwością obiektu, która jest funkcją. W dotychczasowych przykładach nie kopiowaliśmy obiektu z metodą. Spróbujmy to teraz zrobić i wykorzystajmy poznane metody do tworzenia kopii.
let obj = { name: 'scotch.io', exec: function exec() { return true; },}let method1 = Object.assign({}, obj);let method2 = JSON.parse(JSON.stringify(obj));console.log(method1); //Object.assign({}, obj)/* result{ exec: function exec() { return true; }, name: "scotch.io"}*/console.log(method2); // JSON.parse(JSON.stringify(obj))/* result{ name: "scotch.io"}*/
Wynik pokazuje, że Object.assign()
może być użyty do kopiowania metod, natomiast JSON.parse(JSON.stringify(obj))
nie może być użyty.
Kopiowanie obiektów kołowych
Obiekty kołowe to obiekty, które mają właściwości odwołujące się do samych siebie. Użyjmy metod kopiowania obiektów, które poznaliśmy do tej pory, aby utworzyć kopie obiektu kołowego i zobaczmy, czy to działa.
Użycie JSON.parse(JSON.stringify(object))
Spróbujmy JSON.parse(JSON.stringify(object))
:
// circular objectlet obj = { a: 'a', b: { c: 'c', d: 'd', },}obj.c = obj.b;obj.e = obj.a;obj.b.c = obj.c;obj.b.d = obj.b;obj.b.e = obj.b.c;let newObj = JSON.parse(JSON.stringify(obj));console.log(newObj);
Oto wynik:
JSON.parse(JSON.stringify(obj))
wyraźnie nie działa dla okrągłych obiektów.
Użycie Object.assign()
Spróbujmy Object.assign()
:
// circular objectlet obj = { a: 'a', b: { c: 'c', d: 'd', },}obj.c = obj.b;obj.e = obj.a;obj.b.c = obj.c;obj.b.d = obj.b;obj.b.e = obj.b.c;let newObj2 = Object.assign({}, obj);console.log(newObj2);
Oto wynik:
Object.assign()
działa dobrze dla płytkiego kopiowania okrągłych obiektów, ale nie działałby dla głębokiego kopiowania. Zapraszam do zbadania circular object tree
na konsoli przeglądarki. Jestem pewien, że znajdziesz tam wiele interesującej pracy.
Używanie elementów rozproszonych ( … )
ES6 ma już zaimplementowane elementy spoczynkowe do przypisywania destruktorów tablicowych i elementy rozproszone do literałów tablicowych. Spójrz na implementację elementu spread na tablicy tutaj:
const array = ;const newArray = ;console.log(newArray);// Result //
Właściwość spread dla literałów obiektów jest obecnie propozycją Stage 3 dla ECMAScript. Właściwości rozprzestrzeniania w inicjalizatorach obiektów kopiują własne enumerowalne właściwości z obiektu źródłowego na obiekt docelowy. Poniższy przykład pokazuje, jak łatwo byłoby skopiować obiekt, gdy propozycja zostanie zaakceptowana.
let obj = { one: 1, two: 2,}let newObj = { ...z };// { one: 1, two: 2 }
Uwaga: To będzie skuteczne tylko dla płytkiego kopiowania
Wniosek
Kopiowanie obiektów w JavaScript może być dość zniechęcające, szczególnie jeśli jesteś nowy w JavaScript i nie znasz języka. Mam nadzieję, że ten artykuł pomógł Ci zrozumieć i uniknąć przyszłych pułapek, które możesz napotkać kopiując obiekty. Jeśli masz jakąś bibliotekę lub kawałek kodu, który pozwala osiągnąć lepszy rezultat, podziel się nim ze społecznością. Szczęśliwego kodowania!