Platforma jest teraz wyposażona w StructuredClone() – wbudowaną funkcję kopiowania głębokiego.
Przez najdłuższy czas utworzenie głębokiej kopii wartości JavaScript wymagało odwoływania się do obejścia i bibliotek. Platforma jest teraz wyposażona w wbudowaną funkcję kopiowania głębokiego structuredClone()
.
Płytkie kopie
Kopiowanie wartości w JavaScript prawie zawsze przebiega płytkiej, a nie głębokie. Oznacza to, że zmiany w wartościach głęboko zagnieżdżonych będą widoczne zarówno w kopii, jak i w oryginale.
Jednym ze sposobów utworzenia płytkiej kopii w języku JavaScript jest użycie operatora rozkładu obiektu ...
:
const myOriginal = {
someProp: "with a string value",
anotherProp: {
withAnotherProp: 1,
andAnotherProp: true
}
};
const myShallowCopy = {...myOriginal};
Dodanie lub zmiana właściwości bezpośrednio w płytkiej kopii ma wpływ tylko na kopię, a nie na oryginał:
myShallowCopy.aNewProp = "a new value";
console.log(myOriginal.aNewProp)
// ^ logs `undefined`
Jednak dodanie lub zmiana właściwości głęboko zagnieżdżonej ma wpływ zarówno na kopię, jak i na oryginał:
myShallowCopy.anotherProp.aNewProp = "a new value";
console.log(myOriginal.anotherProp.aNewProp)
// ^ logs `a new value`
Wyrażenie {...myOriginal}
wykona iterację po właściwościach (wyliczanych) myOriginal
przy użyciu operatora rozproszenia. Na podstawie nazwy i wartości właściwości przypisuje je pojedynczo do nowo utworzonego, pustego obiektu. Wynikowy obiekt ma taki sam kształt, ale ma własną kopię listy właściwości i wartości. Wartości również są kopiowane, ale wartości podstawowe są obsługiwane inaczej przez wartości JavaScript niż wartości inne. Aby zacytować MDN:
W języku JavaScript obiekt podstawowy (wartość podstawowa, typ danych) to dane, które nie są obiektami i nie mają metod. Istnieje 7 podstawowych typów danych: string, number, bigint, boolean, undefined, symbol i null.
MDN – podstawowy
Wartości inne niż podstawowe są traktowane jako odwołania, co oznacza, że skopiowanie wartości to tak naprawdę skopiowanie odwołania do tego samego obiektu bazowego, co skutkuje płytkim działaniem kopiowania.
Głębokie kopie
Przeciwieństwem jest płytka treść. Algorytm głębokiego kopiowania również kopiuje po kolei właściwości obiektu, ale wywołuje się rekurencyjnie, gdy znajdzie odwołanie do innego obiektu, tworząc także kopię tego obiektu. Może to być bardzo ważne, aby upewnić się, że dwa fragmenty kodu nie będą miały wspólnego obiektu i nie nieświadomie nie będą manipulowały swoim stanem.
Kiedyś nie było łatwego ani przyjemnego sposobu na utworzenie głębokiej kopii wartości w języku JavaScript. Wiele osób korzystało z bibliotek innych firm, takich jak funkcja cloneDeep()
Lodasha. Najczęstszym rozwiązaniem tego problemu było hakowanie w formacie JSON:
const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));
W rzeczywistości było to tak popularne obejście, że V8 intensywnie zoptymalizowało JSON.parse()
, a konkretnie powyższy wzorzec, aby zapewnić jak najszybsze działanie. Jest szybki, ma jednak kilka wad i usterek:
- Rekurencyjne struktury danych: metoda
JSON.stringify()
odrzuci prośbę, jeśli nadasz jej rekurencyjną strukturę danych. Może się to łatwo zdarzyć podczas pracy z powiązanymi listami lub drzewami. - Wbudowane typy:
JSON.stringify()
spowoduje zgłoszenie, jeśli wartość zawiera inne wbudowane dodatki JS, np.Map
,Set
,Date
,RegExp
lubArrayBuffer
. - Funkcje:
JSON.stringify()
dyskretnie odrzuci funkcje.
Klonowanie strukturalne
Platforma potrzebowała już możliwości tworzenia głębokich kopii wartości JavaScript w kilku miejscach: przechowywanie wartości JS w IndexedDB wymaga jakiejś formy serializacji, by można ją było zapisać na dysku, a później poddać deserializacji w celu przywrócenia wartości JS. Podobnie wysyłanie wiadomości do WebWorker przez postMessage()
wymaga przeniesienia wartości JS z jednego obszaru JS do innego. Używany do tego algorytm nazywa się „ustrukturyzowany klon”, a do niedawna nie był łatwo dostępny dla deweloperów.
Teraz to się zmieniło. Specyfikacja HTML została zmieniona, aby udostępnić funkcję o nazwie structuredClone()
, która działa dokładnie z tym algorytmem, aby umożliwić programistom łatwe tworzenie głębokich kopii wartości JavaScript.
const myDeepCopy = structuredClone(myOriginal);
To wszystko. To cały interfejs API. Więcej szczegółów znajdziesz w artykule MDN.
Funkcje i ograniczenia
Klonowanie strukturalne eliminuje wiele (ale nie wszystkie) wady metody JSON.stringify()
. Klonowanie strukturalne może obsługiwać cykliczne struktury danych oraz obsługuje wiele wbudowanych typów danych. Jest też ogólnie bardziej niezawodne i często szybsze.
Nadal jednak występują w nim ograniczenia, które mogą Cię nie interesować:
- Prototypy: jeśli użyjesz metody
structuredClone()
z wystąpieniem klasy, zwracany będzie zwykły obiekt. ponieważ klonowanie strukturalne odrzuca łańcuch prototypów obiektu. - Funkcje: jeśli obiekt zawiera funkcje,
structuredClone()
zgłosi wyjątekDataCloneError
. - Wartości nie do sklonowania: niektóre wartości nie podlegają klonowaniu strukturalnemu, w szczególności węzły
Error
i DOM. it spowoduje rzutstructuredClone()
.
Jeśli któreś z tych ograniczeń jest przeszkodą w Twoim przypadku użycia, biblioteki takie jak Lodash nadal udostępniają niestandardowe implementacje innych algorytmów do precyzyjnego klonowania, które mogą (ale nie muszą) pasować do Twojego zastosowania.
Wyniki
Nie przeprowadziłem żadnego nowego porównania w ramach mikrotestów porównawczych, ale na początku 2018 roku przeprowadziłem(-am) to jednak przed udostępnieniem structuredClone()
. W tamtym czasie funkcja JSON.parse()
była najszybszą opcją w przypadku bardzo małych obiektów. Zakładam, że to się nie zmieni. Metody wykorzystujące klonowanie strukturalne były (znacznie) szybsze w przypadku większych obiektów. Biorąc pod uwagę, że nowa wersja structuredClone()
nie wymaga nadużywania innych interfejsów API i jest bardziej zaawansowana od JSON.parse()
, zalecamy ustawienie jej jako domyślnej metody tworzenia głębokich kopii.
Podsumowanie
Jeśli musisz utworzyć głęboką kopię wartości w języku JS – na przykład dlatego, że używasz stałych struktur danych lub chcesz mieć pewność, że funkcja może modyfikować obiekt bez wpływu na oryginał – nie musisz już sięgać po sposoby obejścia tego problemu lub biblioteki. Ekosystem JS ma teraz structuredClone()
. Hurra.