Rozprzestrzenianie się obiektów vs. Object.assign

396

Powiedzmy, że mam optionszmienną i chcę ustawić wartość domyślną.

Jaka jest korzyść / wada tych dwóch alternatyw?

Korzystanie z rozkładania obiektów

options = {...optionsDefault, ...options};

Lub używając Object.assign

options = Object.assign({}, optionsDefault, options);

To zatwierdzenie sprawiło, że się zastanawiałem.

Olivier Tassinari
źródło
4
Cóż, pierwszy to proponowana nowa składnia i nie jest częścią ES6, więc zależy to od tego, jaki standard chcesz zastosować.
loganfsmyth
5
Zdefiniuj „ najlepszy ” (ostrożnie, nie
Amit
1
Może również zależeć od tego, jak chcesz go obsługiwać, jeśli działa w środowiskach bez obsługi natywnej. Składnia, którą możesz skompilować. Obiekt lub metoda, która może być potrzebna do wypełniania.
JMM
6
oprócz problemów ze zgodnością Object.assign może mutować oryginalny obiekt, co jest przydatne. spread nie może.
pstanton
2
Aby wyjaśnić komentarz @ pstanton - object.assign może modyfikować istniejący obiekt docelowy (nadpisywać właściwości ze źródła, pozostawiając nienaruszone inne właściwości); nie dotyka obiektu źródłowego . Najpierw przeczytałem jego „oryginalny obiekt” jako „obiekt źródłowy”, więc piszę tę notatkę dla każdego, kto podobnie ją źle odczytał. :)
ToolmakerSteve

Odpowiedzi:

328

To niekoniecznie jest wyczerpujące.

Rozłóż składnię

options = {...optionsDefault, ...options};

Zalety:

  • Jeśli tworzysz kod do wykonania w środowiskach bez natywnej obsługi, możesz po prostu skompilować tę składnię (w przeciwieństwie do korzystania z funkcji wypełniania wielokrotnego). (Na przykład z Babel.)

  • Mniej gadatliwy.

Niedogodności:

  • Kiedy pierwotnie napisano tę odpowiedź, była to propozycja , która nie była znormalizowana. Korzystając z propozycji, zastanów się, co byś zrobił, gdybyś napisał z nim teraz kod, który nie jest standaryzowany ani nie zmienia się w miarę zbliżania się do normalizacji. Od tego czasu została znormalizowana w ES2018.

  • Dosłowne, a nie dynamiczne.


Object.assign()

options = Object.assign({}, optionsDefault, options);

Zalety:

  • Standaryzowany.

  • Dynamiczny. Przykład:

    var sources = [{a: "A"}, {b: "B"}, {c: "C"}];
    options = Object.assign.apply(Object, [{}].concat(sources));
    // or
    options = Object.assign({}, ...sources);

Niedogodności:

  • Więcej pełnych słów.
  • W przypadku kodu autorskiego do wykonania w środowiskach bez natywnego wsparcia należy wypełnić wielopunktowo.

To zatwierdzenie sprawiło, że się zastanawiałem.

To nie jest bezpośrednio związane z tym, o co pytasz. Ten kod nie był używany Object.assign(), tylko kod użytkownika ( object-assign), który robi to samo. Wygląda na to, że kompilują ten kod z Babelem (i łączą go z Webpackiem), o tym właśnie mówiłem: składnię, którą można po prostu skompilować. Najwyraźniej woleli to od uwzględnienia object-assignjako zależności, która byłaby częścią ich kompilacji.

JMM
źródło
15
Warto zauważyć, że rozkład reszty obiektów został przeniesiony do etapu 3, więc prawdopodobnie zostanie ujednolicony w przyszłości twitter.com/sebmarkbage/status/781564713750573056
williaster
11
@JMM Nie jestem pewien, czy widzę „Więcej pełnych informacji”. jako wada.
DiverseAndRemote.com
6
@Omar cóż, myślę, że właśnie udowodniłeś, że to opinia :) Jeśli ktoś nie rozpoznaje tej przewagi, to tak, wszyscy inni są równi, mogą po prostu skorzystać Object.assign(). Lub możesz ręcznie iterować tablicę obiektów i ich własnych rekwizytów ręcznie przypisując je do celu i sprawiając, że będzie jeszcze bardziej gadatliwy: P
JMM 07.04.17
7
jak wspomniano @JMM, jest teraz w węźle specyfikacji ES2018.green/#ES2018-features-object-rest-spread-properties
Sebastien H.
2
„Każdy bajt się liczy” To jest to, czym są minimalizatory / uglify dla @yzorg
DiverseAndRemote.com
171

W przypadku obiektu referencyjnego odpoczynek / rozkład jest finalizowany w ECMAScript 2018 jako etap 4. Propozycję można znaleźć tutaj .

W większości przypadków resetowanie i rozkładanie obiektów działa w ten sam sposób, kluczową różnicą jest to, że rozkład definiuje właściwości, a Object.assign () je ustawia . Oznacza to, że Object.assign () wyzwala ustawiacze.

Warto pamiętać, że poza tym reszta obiektu / rozkład 1: 1 odwzorowuje na Object.assign () i działa inaczej niż rozkład tablic (iterowalny). Na przykład przy rozkładaniu tablicy rozkładane są wartości zerowe. Jednak przy użyciu rozkładania obiektów wartości zerowe są po cichu rozkładane na nic.

Przykład rozprzestrzeniania się macierzy (iterowalnej)

const x = [1, 2, null , 3];
const y = [...x, 4, 5];
const z = null;

console.log(y); // [1, 2, null, 3, 4, 5];
console.log([...z]); // TypeError

Przykład rozprzestrzeniania się obiektu

const x = null;
const y = {a: 1, b: 2};
const z = {...x, ...y};

console.log(z); //{a: 1, b: 2}

Jest to zgodne ze sposobem działania Object.assign (), oba po cichu wykluczają wartość zerową bez błędu.

const x = null;
const y = {a: 1, b: 2};
const z = Object.assign({}, x, y);

console.log(z); //{a: 1, b: 2}
tomhughes
źródło
10
To powinna być wybrana odpowiedź.
Evan Plaice,
4
To powinna być odpowiedź ... teraz jest przyszłość.
Zachary Abresch
1
To jest właściwa odpowiedź. Główną różnicą jest Object.assignużycie seterów. Object.assign({set a(v){this.b=v}, b:2}, {a:4}); // {b: 4}vs.{...{set a(v){this.b=v}, b:2}, ...{a:4}}; // {a: 4, b: 2}
David Boho
1
"Przyszłość jest teraz!" - George Allen Jutro jest za późno.
ruffin
1
Wykazanie różnicy w wartościach zerowych to „jabłka i pomarańcze” - nie jest to sensowne porównanie. W przypadku tablicy null jest elementem tablicy. W przypadku rozproszenia null jest całym obiektem. Prawidłowe porównanie byłoby x mieć właściwość zerowej : const x = {c: null};. W takim przypadku, AFAIK, chcielibyśmy zobaczyć zachowanie jak tablicy: //{a: 1, b: 2, c: null}.
ToolmakerSteve,
39

Myślę, że jedną wielką różnicą między operatorem rozprzestrzeniania i Object.assigntym, o którym nie wspominają obecne odpowiedzi, jest to, że operator rozprzestrzeniania nie skopiuje prototypu obiektu źródłowego do obiektu docelowego. Jeśli chcesz dodać właściwości do obiektu i nie chcesz zmieniać jego instancji, musisz użyć Object.assign. Poniższy przykład powinien to wykazać:

const error = new Error();
error instanceof Error // true

const errorExtendedUsingSpread = {
  ...error,
  ...{
    someValue: true
  }
};
errorExtendedUsingSpread instanceof Error; // false

const errorExtendedUsingAssign = Object.assign(error, {
  someValue: true
});
errorExtendedUsingAssign instanceof Error; // true

Sean Dawson
źródło
„nie utrzyma nienaruszonego prototypu” - na pewno tak, ponieważ niczego nie modyfikuje. W przykładzie errorExtendedUsingAssign === error, ale errorExtendedUsingSpreadjest nowy obiekt (a prototyp nie został skopiowany).
maaartinus
2
@maaartinus Masz rację, prawdopodobnie źle to sformułowałem. Miałem na myśli, że prototypu nie ma na skopiowanym obiekcie. Mogę to poprawić, aby było jaśniej.
Sean Dawson,
Czy następujący sposób „płytkiego klonowania” jest przedmiotem w swojej klasie? let target = Object.create(source); Object.assign(target, source);
ToolmakerSteve,
@ToolmakerSteve Tak, skopiuje wszystkie „własne właściwości” obiektu, przez co skutecznie stanie się płytkim klonem. Widzieć: stackoverflow.com/questions/33692912/…
Sean Dawson
12

Jak wspomnieli inni, w tym momencie pisania, Object.assign()wymaga wypełnienia, a rozprzestrzenianie się obiektów ...wymaga transpilacji (i być może również wypełnienia), aby działało.

Rozważ ten kod:

// Babel wont touch this really, it will simply fail if Object.assign() is not supported in browser.
const objAss = { message: 'Hello you!' };
const newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

// Babel will transpile with use to a helper function that first attempts to use Object.assign() and then falls back.
const objSpread = { message: 'Hello you!' };
const newObjSpread = {...objSpread, dev: true };
console.log(newObjSpread);

Oba wytwarzają tę samą moc wyjściową.

Oto dane wyjściowe z Babel do ES5:

var objAss = { message: 'Hello you!' };
var newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var objSpread = { message: 'Hello you!' };
var newObjSpread = _extends({}, objSpread, { dev: true });
console.log(newObjSpread);

To moje dotychczasowe zrozumienie. Object.assign()jest w rzeczywistości znormalizowany, a ponieważ rozprzestrzenianie się obiektów ...nie jest jeszcze. Jedynym problemem jest obsługa przeglądarek dla tych pierwszych, aw przyszłości także dla tych drugich.

Graj tutaj kodem

Mam nadzieję że to pomoże.

Michael Giovanni Pumo
źródło
1
Dziękuję Ci! Twój przykładowy kod ułatwia podejmowanie decyzji w moim kontekście. Transpiler (babel lub maszynopis) sprawia, że ​​operator rozprzestrzeniania jest bardziej kompatybilny z przeglądarkami, włączając pollyfill do kodu wbudowanego. Tuż za zainteresowanie wersja maszynopis transpiled jest praktycznie taka sama jak Babel: typescriptlang.org/play/...
Mark Whitfeld
2
hmm ... czy twoje dwa przypadki nie dałyby takich samych rezultatów? w pierwszym przypadku kopiujesz właściwości z jednego obiektu do drugiego, aw drugim tworzysz nowy obiekt. Object.assign zwraca cel, więc w pierwszym przypadku objAss i newObjAss są takie same.
Kevin B,
Dodanie nowego pierwszego parametru {}powinno naprawić niespójność.
Kevin B,
11

Operator rozkładania obiektów (...) nie działa w przeglądarkach, ponieważ nie jest jeszcze częścią żadnej specyfikacji ES, tylko propozycją. Jedyną opcją jest skompilowanie go z Babel (lub czymś podobnym).

Jak widać, to po prostu cukier składniowy nad Object.assign ({}).

O ile widzę, są to ważne różnice.

  • Object.assign działa w większości przeglądarek (bez kompilacji)
  • ... dla obiektów nie jest znormalizowany
  • ... chroni cię przed przypadkowym mutowaniem obiektu
  • ... polifill Object. przypisze do przeglądarki bez niego
  • ... potrzebuje mniej kodu, aby wyrazić ten sam pomysł
Karthick Kumar
źródło
34
To nie jest cukier składniowy Object.assign, ponieważ operator rozkładania zawsze da ci nowy obiekt.
MaxArt
4
Tak naprawdę jestem zaskoczony, że inni ludzie nie podkreślają więcej różnicy zmienności. Pomyśl o wszystkich godzinach pracy programisty utraconych podczas debugowania przypadkowych mutacji za pomocą Object.assign
deepelement
Jest to teraz obsługiwane w większości nowoczesnych przeglądarek (podobnie jak w innych ES6): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
akmalhakimi1991
11

Chciałbym podsumować stan funkcji ES „rozprzestrzeniania obiektów rozproszonych” w przeglądarkach i ekosystemie za pomocą narzędzi.

Spec

Przeglądarki: wkrótce w Chrome, SF, Firefox (wersja 60, IIUC)

  • Obsługa przeglądarki dla „właściwości rozprzestrzeniania” dostarczanych w Chrome 60 , w tym w tym scenariuszu.
  • Obsługa tego scenariusza NIE działa w bieżącym Firefoksie (59), ale DZIAŁA w mojej Firefox Developer Edition. Więc wierzę, że będzie dostępny w Firefox 60.
  • Safari: nie przetestowano, ale Kangax twierdzi, że działa w Safari Desktop 11.1, ale nie SF 11
  • iOS Safari: nie skonfigurowano, ale Kangax twierdzi, że działa w iOS 11.3, ale nie w iOS 11
  • jeszcze nie w Edge

Narzędzia: Węzeł 8.7, TS 2.1

Spinki do mankietów

Próbka kodu (podwaja się jako test zgodności)

var x = { a: 1, b: 2 };
var y = { c: 3, d: 4, a: 5 };
var z = {...x, ...y};
console.log(z); // { a: 5, b: 2, c: 3, d: 4 }

Ponownie: w momencie pisania tego przykładu próbka działa bez transpilacji w Chrome (60+), Firefox Developer Edition (wersja zapoznawcza Firefox 60) i Node (8.7+).

Po co odpowiadać?

Piszę to 2,5 roku po pierwotnym pytaniu. Ale miałem to samo pytanie i właśnie tutaj wysłał mnie Google. Jestem niewolnikiem misji SO, aby poprawić długi ogon.

Ponieważ jest to rozszerzenie składni „rozprzestrzeniania się macierzy”, bardzo trudno było google i trudno znaleźć w tabelach zgodności. Najbliższe, jakie mogłem znaleźć, to „rozpiętość właściwości” Kangaxa , ale ten test nie ma dwóch rozkładów w tym samym wyrażeniu (nie scalenia). Poza tym nazwa na stronach statusu propozycji / wersji roboczych / przeglądarki używa „właściwości spread”, ale wydaje mi się, że była to „pierwsza zasada”, do której doszła społeczność po propozycjach użycia składni spreadu do „scalania obiektów”. (Co może wyjaśniać, dlaczego tak trudno jest google.) Więc dokumentuję tutaj moje odkrycie, aby inni mogli przeglądać, aktualizować i kompilować linki dotyczące tej konkretnej funkcji. Mam nadzieję, że się uda. Proszę o pomoc w rozpowszechnianiu wiadomości o lądowaniu w specyfikacji oraz w przeglądarkach.

Na koniec dodałbym te informacje jako komentarz, ale nie mogłem ich edytować bez naruszenia pierwotnych intencji autorów. W szczególności nie mogę edytować komentarza @ ChillyPenguin bez utraty jego zamiaru poprawienia @RichardSchulte. Ale lata później Richard miał rację (moim zdaniem). Dlatego piszę tę odpowiedź, mając nadzieję, że w końcu zyska przyczepność do starych odpowiedzi (może to potrwać lata, ale przecież o to chodzi w efekcie długiego ogona ).

Yzorg
źródło
3
Twoja sekcja „dlaczego odpowiedź” prawdopodobnie nie jest potrzebna
LocustHorde
@LocustHorde Może mógłbym przenieść akapit drugi (dlaczego ten temat jest tak trudny do przeszukania w Google) do własnej sekcji. Wtedy reszta może pasować do komentarza.
yzorg
8

UWAGA: Spread to NIE tylko cukier składniowy wokół Object.assign. Działają znacznie inaczej za kulisami.

Object.assign stosuje ustawiacze do nowego obiektu, Spread nie. Ponadto obiekt musi być iterowalny.

Kopiuj Użyj tej opcji, jeśli potrzebujesz wartości obiektu w tej chwili i nie chcesz, aby ta wartość odzwierciedlała wszelkie zmiany wprowadzone przez innych właścicieli obiektu.

Skorzystaj z niego, aby utworzyć płytką kopię obiektu. Dobra praktyka, aby zawsze ustawiać niezmienne właściwości do kopiowania - ponieważ wersje zmienne mogą być przekazywane do niezmiennych właściwości, kopiowanie zapewni, że zawsze będziesz mieć do czynienia z niezmiennym obiektem

Przypisz Przypisz jest w pewnym stopniu przeciwieństwem kopiowania. Przypisanie wygeneruje obiekt ustawiający, który przypisuje wartość do zmiennej instancji bezpośrednio, zamiast kopiować lub zachowywać ją. Podczas wywoływania obiektu pobierającego właściwość przypisania zwraca odwołanie do rzeczywistych danych.

Charles Owen
źródło
3
Dlaczego napis „Kopiuj”? Co to za śmiałe nagłówki.
Wydaje
2

Inne odpowiedzi są stare, nie można uzyskać dobrej odpowiedzi.
Poniższy przykład dotyczy literałów obiektowych, pomaga, jak oba mogą się uzupełniać i jak nie mogą się uzupełniać (dlatego różnica):

var obj1 = { a: 1,  b: { b1: 1, b2: 'b2value', b3: 'b3value' } };

// overwrite parts of b key
var obj2 = {
      b: {
        ...obj1.b,
        b1: 2
      }
};
var res2 = Object.assign({}, obj1, obj2); // b2,b3 keys still exist
document.write('res2: ', JSON.stringify (res2), '<br>');
// Output:
// res2: {"a":1,"b":{"b1":2,"b2":"b2value","b3":"b3value"}}  // NOTE: b2,b3 still exists

// overwrite whole of b key
var obj3 = {
      b: {
        b1: 2
      }
};
var res3 = Object.assign({}, obj1, obj3); // b2,b3 keys are lost
document.write('res3: ', JSON.stringify (res3), '<br>');
// Output:
  // res3: {"a":1,"b":{"b1":2}}  // NOTE: b2,b3 values are lost

Kilka innych małych przykładów tutaj, także dla tablicy i obiektu:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Manohar Reddy Poreddy
źródło
1

Jest to teraz część ES6, dlatego jest znormalizowana i jest również udokumentowana w MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

Jest bardzo wygodny w użyciu i ma wiele sensu obok niszczenia obiektów.

Jedyną pozostałą zaletą wymienioną powyżej są dynamiczne możliwości Object.assign (), jednak jest to tak proste, jak rozkładanie tablicy wewnątrz dosłownego obiektu. W skompilowanym wyjściu Babel używa dokładnie tego, co pokazano w Object.assign ()

Tak więc poprawną odpowiedzią byłoby użycie rozproszenia obiektów, ponieważ jest on teraz znormalizowany, szeroko stosowany (patrz reaguj, redux itp.), Jest łatwy w użyciu i ma wszystkie funkcje Object.assign ()

Rikki Schulte
źródło
2
Nie, to nie jest część ES6. Podany przez Ciebie link odnosi się do wykorzystania przez operatora rozkładania tylko w tablicach . Korzystanie z operatora rozkładania na obiektach jest obecnie propozycją etapu 2 (tj. Wersji roboczej), jak wyjaśniono w odpowiedzi JMM.
ChillyPenguin
1
Nie sądzę, żebym kiedykolwiek widział odpowiedź tak całkowicie opartą na fałszywych informacjach. Nawet rok później nie jest to częścią specyfikacji ES i nie jest obsługiwane w większości środowisk.
3ocen,
Chilly i 3o okazali się nie tak, Richard ma rację. Wsparcie dla przeglądarki i narzędzia lądują, ale minęło 1,5 roku od odpowiedzi Richarda. Zobacz moją nową odpowiedź na podsumowanie wsparcia z marca 2018 r.
yzorg
0

Chciałbym dodać ten prosty przykład, gdy musisz użyć Object.assign.

class SomeClass {
  constructor() {
    this.someValue = 'some value';
  }

  someMethod() {
    console.log('some action');
  }
}


const objectAssign = Object.assign(new SomeClass(), {});
objectAssign.someValue; // ok
objectAssign.someMethod(); // ok

const spread = {...new SomeClass()};
spread.someValue; // ok
spread.someMethod(); // there is no methods of SomeClass!

Nie może być jasne, gdy używasz JavaScript. Ale dzięki TypeScript łatwiej jest utworzyć instancję jakiejś klasy

const spread: SomeClass = {...new SomeClass()} // Error
zemil
źródło