Zarówno Object.assign, jak i Object spread powodują tylko płytkie scalanie.
Przykład problemu:
// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }
Dane wyjściowe są zgodne z oczekiwaniami. Jeśli jednak spróbuję:
// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }
Zamiast
{ a: { a: 1, b: 1 } }
dostajesz
{ a: { b: 1 } }
x jest całkowicie nadpisane, ponieważ składnia spreadu przechodzi tylko o jeden poziom w głąb. Tak samo jest z Object.assign()
.
Czy jest na to sposób?
javascript
spread-syntax
Mikrofon
źródło
źródło
Odpowiedzi:
Nie.
źródło
Wiem, że to trochę stary problem, ale najłatwiejsze rozwiązanie w ES2015 / ES6, jakie mogłem wymyślić, było w rzeczywistości dość proste, używając Object.assign (),
Mam nadzieję, że to pomaga:
Przykładowe użycie:
Niezmienna wersja tego znajduje się w odpowiedzi poniżej.
Zauważ, że doprowadzi to do nieskończonej rekurencji w odniesieniu do okólników. Istnieje kilka świetnych odpowiedzi na temat wykrywania okrągłych odniesień, jeśli uważasz, że napotkasz ten problem.
źródło
item !== null
nie powinno być potrzebne w środkuisObject
, ponieważitem
jest już sprawdzane pod kątem prawdziwości na początku warunkuObject.assign(target, { [key]: {} })
jeśli mogłoby tak byćtarget[key] = {}
?target[key] = source[key]
zamiastObject.assign(target, { [key]: source[key] });
target
. Na przykładmergeDeep({a: 3}, {a: {b: 4}})
spowoduje powiększenieNumber
obiektu, co oczywiście nie jest pożądane. PonadtoisObject
nie akceptuje tablic, ale akceptuje każdy inny rodzimy typ obiektu, taki jak np.Date
, Który nie powinien być głęboko kopiowany.Możesz użyć scalania Lodash :
źródło
{ 'a': [{ 'b': 2 }, { 'c': 3 }, { 'd': 4 }, { 'e': 5 }] }
?{ 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
jest prawidłowy, ponieważ łączymy elementy tablicy. Element0
ofobject.a
Is{b: 2}
element0
oother.a
to{c: 3}
. Gdy te dwa są scalone, ponieważ mają ten sam indeks tablicy, wynikiem jest{ 'b': 2, 'c': 3 }
, który jest elementem0
w nowym obiekcie.Problem nie jest trywialny, jeśli chodzi o obiekty hosta lub dowolny obiekt bardziej złożony niż worek wartości
Kolejna rzecz, o której należy pamiętać: wykresy obiektów zawierające cykle. Zwykle nie jest trudno sobie z tym poradzić - po prostu przechowywać
Set
już odwiedzone obiekty źródłowe - ale często zapominane.Prawdopodobnie powinieneś napisać funkcję głębokiego scalania, która oczekuje tylko prymitywnych wartości i prostych obiektów - co najwyżej tych typów, które może obsłużyć algorytm ustrukturyzowanego klonowania - jako źródeł scalania. Rzuć, jeśli napotka coś, czego nie może obsłużyć lub po prostu przypisać przez odniesienie zamiast głębokiego scalania.
Innymi słowy, nie ma jednego uniwersalnego algorytmu, musisz albo rzucić własny, albo poszukać metody bibliotecznej, która pokrywa twoje przypadki użycia.
źródło
Oto niezmienna (nie modyfikuje danych wejściowych) wersja odpowiedzi @ Salakar. Przydatne, jeśli wykonujesz czynności typu programowania funkcjonalnego.
źródło
key
jako nazwa właściwości, później zmieni „klucz” na nazwę właściwości. Patrz: es6-features.org/#ComputedPropertyNamesisObject
nie trzeba by sprawdzić&& item !== null
na końcu, bo zaczyna linii zitem &&
, nie?mergedDeep
danych wyjściowych (myślę). Np.const target = { a: 1 }; const source = { b: { c: 2 } }; const merged = mergeDeep(target, source);
merged.b.c; // 2
source.b.c = 3;
merged.b.c; // 3
Czy to problem? Nie mutuje danych wejściowych, ale wszelkie przyszłe mutacje na wejściach mogą mutować dane wyjściowe i odwrotnie w / mutacje na wyjściowe dane mutacji. Jednak za to, co jest warte, ramda zachowuje sięR.merge()
tak samo.Ponieważ ten problem jest nadal aktywny, oto inne podejście:
źródło
prev[key] = pVal.concat(...oVal);
naprev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index);
reduce
nie używa .Wiem, że jest już wiele odpowiedzi i jak wiele komentarzy twierdzi, że nie zadziałają. Jedyny konsensus jest taki, że jest tak skomplikowany, że nikt nie ustanowił dla niego standardu . Jednak większość zaakceptowanych odpowiedzi w SO ujawnia „proste sztuczki”, które są szeroko stosowane. Tak więc, dla wszystkich takich jak ja, którzy nie są ekspertami, ale chcą pisać bezpieczniejszy kod, chwytając nieco więcej o złożoności javascript, postaram się rzucić nieco światła.
Zanim ubrudzimy sobie ręce, wyjaśnię 2 punkty:
Object.assign
robi.Odpowiedzi z
for..in
lubObject.keys
są myląceWykonywanie głębokiej kopii wydaje się tak podstawową i powszechną praktyką, że spodziewamy się, że znajdziemy jedną linijkę lub przynajmniej szybką wygraną poprzez prostą rekurencję. Nie oczekujemy, że powinniśmy potrzebować biblioteki lub napisać niestandardową funkcję zawierającą 100 wierszy.
Kiedy po raz pierwszy przeczytałem odpowiedź Salakara , naprawdę pomyślałem, że mogę zrobić to lepiej i prościej (można to porównać z
Object.assign
dalejx={a:1}, y={a:{b:1}}
). Potem przeczytałem odpowiedź the8472 i pomyślałem ... że nie da się tak łatwo uciec, poprawa już udzielonych odpowiedzi nie zaprowadzi nas daleko.Odłóżmy na bok głębokie kopiowanie i rekurencję. Zastanów się, jak (błędnie) ludzie analizują właściwości, aby skopiować bardzo prosty obiekt.
Object.keys
pominie własne, niepoliczalne właściwości, własne właściwości z kluczem symbolicznym i wszystkie właściwości prototypu. Może być dobrze, jeśli twoje obiekty nie mają żadnego z nich. Ale pamiętaj, żeObject.assign
obsługuje własne wyliczalne właściwości z kluczami symbolicznymi. Twoja niestandardowa kopia straciła swój rozkwit.for..in
dostarczy właściwości źródła, jego prototypu i pełnego łańcucha prototypów bez Twojej woli (lub wiedzy). Twój cel może mieć zbyt wiele właściwości, mieszając właściwości prototypu i właściwości własne.Jeśli piszesz funkcję ogólnego przeznaczenia i nie używasz
Object.getOwnPropertyDescriptors
,Object.getOwnPropertyNames
,Object.getOwnPropertySymbols
lubObject.getPrototypeOf
, jesteś najprawdopodobniej robi to źle.Rzeczy do rozważenia przed napisaniem funkcji
Najpierw upewnij się, że rozumiesz, czym jest obiekt JavaScript. W Javascript obiekt składa się z własnych właściwości i (macierzystego) obiektu prototypowego. Z kolei obiekt prototypowy składa się z własnych właściwości i obiektu prototypowego. I tak dalej, definiując łańcuch prototypów.
Właściwość jest parą klucza (
string
lubsymbol
) i deskryptora (value
lubget
/set
akcesorium i atrybutów podobnychenumerable
).Wreszcie istnieje wiele rodzajów obiektów . Możesz chcieć inaczej obsługiwać obiekt Obiekt od obiektu Data lub obiekt Funkcja.
Pisząc swoją głęboką kopię, powinieneś przynajmniej odpowiedzieć na następujące pytania:
W moim przykładzie uważam, że tylko
object Object
s są głębokie , ponieważ inne obiekty utworzone przez innych konstruktorów mogą nie być odpowiednie do dogłębnego spojrzenia. Dostosowane z tego SO .I zrobiłem
options
obiekt, aby wybrać, co skopiować (do celów demonstracyjnych).Proponowana funkcja
Możesz to przetestować w tym narzędziu .
Można tego użyć w następujący sposób:
źródło
Używam lodash:
źródło
_cloneDeep(value1).merge(value2)
Oto implementacja TypeScript:
I testy jednostkowe:
źródło
Oto kolejne rozwiązanie ES6, współpracujące z obiektami i tablicami.
źródło
Pakiet deepmerge npm wydaje się być najczęściej używaną biblioteką do rozwiązania tego problemu: https://www.npmjs.com/package/deepmerge
źródło
Chciałbym przedstawić całkiem prostą alternatywę dla ES5. Funkcja otrzymuje 2 parametry -
target
isource
muszą być typu „obiekt”.Target
będzie wynikowym obiektem.Target
zachowuje wszystkie swoje oryginalne właściwości, ale ich wartości mogą być modyfikowane.przypadki:
target
nie masource
właściwości,target
otrzymuje ją;target
masource
właściwość itarget
&source
nie są oba obiekty (3 przypadkach na 4),target
„s nieruchomość zostanie nadpisane;target
masource
właściwość i oba są obiektami / tablicami (1 pozostały przypadek), wówczas rekurencja polega na scaleniu dwóch obiektów (lub konkatenacji dwóch tablic);rozważ także następujące kwestie :
Jest przewidywalny, obsługuje typy pierwotne, a także tablice i obiekty. Ponieważ możemy scalić 2 obiekty, myślę, że możemy scalić więcej niż 2 za pomocą funkcji zmniejszania .
spójrz na przykład (i pobaw się nim, jeśli chcesz) :
Istnieje ograniczenie - długość stosu wywołań przeglądarki. Nowoczesne przeglądarki generują błąd na bardzo głębokim poziomie rekurencji (pomyśl o tysiącach zagnieżdżonych wywołań). Możesz również dowolnie traktować sytuacje takie jak tablica + obiekt itp., Dodając nowe warunki i sprawdzanie typu.
źródło
Jeśli używasz ImmutableJS , możesz użyć
mergeDeep
:źródło
Jeśli jako rozwiązanie można zastosować biblioteki npm , zaawansowane scalanie obiektów pozwala naprawdę głęboko scalać obiekty i dostosowywać / zastępować każdą akcję scalania za pomocą znanej funkcji wywołania zwrotnego. Jego głównym założeniem jest coś więcej niż tylko głębokie scalanie - co dzieje się z wartością, gdy dwa klucze są takie same ? Ta biblioteka się tym zajmuje - kiedy dwa klucze się zderzają,
object-merge-advanced
waży typy, starając się zachować jak najwięcej danych po scaleniu:Klucz pierwszego argumentu wejściowego jest oznaczony # 1, drugi argument - # 2. W zależności od każdego typu wybiera się jeden dla wartości klucza wyniku. Na schemacie „obiekt” oznacza zwykły obiekt (nie tablicę itp.).
Kiedy klucze się nie kolidują, wszystkie wprowadzają wynik.
Z fragmentu przykładowego, jeśli użyłeś
object-merge-advanced
do scalenia fragmentu kodu:Algorytm rekurencyjnie przegląda wszystkie klucze obiektów wejściowych, porównuje i buduje oraz zwraca nowy scalony wynik.
źródło
Poniższa funkcja tworzy głęboką kopię obiektów, obejmuje kopiowanie prymitywów, tablic oraz obiektów
źródło
Proste rozwiązanie z ES5 (zastąp istniejącą wartość):
źródło
Większość przykładów tutaj wydaje się zbyt skomplikowana, używam jednego z utworzonych przeze mnie TypeScript, myślę, że powinien on obejmować większość przypadków (obsługuję tablice jako zwykłe dane, po prostu je zastępuję).
To samo w zwykłym JS, na wszelki wypadek:
Oto moje przypadki testowe, które pokazują, jak możesz z nich skorzystać
Daj mi znać, jeśli uważasz, że brakuje mi niektórych funkcji.
źródło
Jeśli chcesz mieć jedną linijkę bez konieczności posiadania ogromnej biblioteki, takiej jak lodash, sugeruję skorzystanie z deepmerge . (
npm install deepmerge
)Następnie możesz to zrobić
dostać
Zaletą jest to, że od razu zawiera napisy do TypeScript. Pozwala także łączyć tablice . To jest naprawdę wszechstronne rozwiązanie.
źródło
Możemy użyć $ .extend (true, object1, object2) do głębokiego scalenia. Wartość true oznacza rekurencyjne scalenie dwóch obiektów, modyfikując pierwszy.
$ extension (true, target, object)
źródło
jQuery.isPlainObject()
. Ujawnia to złożoność określania, czy coś jest prostym przedmiotem, którego większość odpowiedzi tutaj omija z daleka. Zgadnij, w jakim języku jest napisane jQuery?Oto proste, proste rozwiązanie, które działa jak
Object.assign
po prostu deeep i działa na tablicę, bez żadnych modyfikacjiPrzykład
źródło
Miałem ten problem podczas ładowania buforowanego stanu redux. Gdybym tylko załadował stan buforowany, napotkałbym błędy dla nowej wersji aplikacji ze zaktualizowaną strukturą stanu.
Wspomniano już, że lodash oferuje
merge
funkcję, z której korzystałem:źródło
Wiele odpowiedzi wykorzystuje dziesiątki linii kodu lub wymaga dodania nowej biblioteki do projektu, ale jeśli użyjesz rekurencji, to tylko 4 linie kodu.
Obsługa tablic: powyższa wersja zastępuje stare wartości tablic nowymi. Jeśli chcesz zachować stare wartości tablic i dodać nowe, po prostu dodaj
else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])
blok nadelse
statamentem i wszystko gotowe.źródło
Oto kolejny, który właśnie napisałem, który obsługuje tablice. To ich konkretyzuje.
źródło
Użyj tej funkcji:
źródło
Ramda, która jest ładną biblioteką funkcji javascript, ma mergeDeepLeft i mergeDeepRight. Każda z nich działa całkiem nieźle na ten problem. Zajrzyj do dokumentacji tutaj: https://ramdajs.com/docs/#mergeDeepLeft
Do konkretnego przykładu, o którym mowa, możemy użyć:
źródło
Test jednostkowy:
źródło
Znalazłem tylko 2-liniowe rozwiązanie, aby uzyskać głębokie scalenie w javascript. Daj mi znać, jak Ci się to uda.
Obiekt Temp wydrukuje {a: {b: 'd', e: 'f', x: 'y'}}
źródło
merge({x:{y:{z:1}}}, {x:{y:{w:2}}})
. Il również nie zaktualizuje istniejących wartości w obj1, jeśli obj2 je ma, na przykład za pomocąmerge({x:{y:1}}, {x:{y:2}})
.Czasami nie potrzebujesz głębokiego łączenia, nawet jeśli tak uważasz. Na przykład, jeśli masz domyślną konfigurację z zagnieżdżonymi obiektami i chcesz ją głęboko rozszerzyć o własną konfigurację, możesz dla tego stworzyć klasę. Koncepcja jest bardzo prosta:
Możesz przekonwertować go na funkcję (nie konstruktor).
źródło
Jest to tanie głębokie scalanie, które wykorzystuje tak mało kodu, jak mogłem sobie wyobrazić. Każde źródło zastępuje poprzednią właściwość, jeśli istnieje.
źródło
Używam następującej krótkiej funkcji do głębokiego scalania obiektów.
Działa świetnie dla mnie.
Autor całkowicie wyjaśnia, jak tu działa.
źródło