Objektverteilung vs. Object.assign


396

Angenommen, ich habe eine optionsVariable und möchte einen Standardwert festlegen.

Was ist der Vor- / Nachteil dieser beiden Alternativen?

Verwenden der Objektverteilung

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

Oder mit Object.assign

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

Dies ist das Commit , das mich wundern ließ.


4
Nun, die erste ist eine vorgeschlagene neue Syntax und ist nicht Teil von ES6. Sie hängt also davon ab, welchen Standard Sie einhalten möchten.
Loganfsmyth

5
Definieren Sie "am besten " (vorsichtig, am Ende keine meinungsbasierte Frage :-)
Amit

1
Kann auch davon abhängen, wie Sie es unterstützen möchten, wenn Sie in Umgebungen ohne native Unterstützung ausgeführt werden. Syntax, die Sie möglicherweise nur kompilieren können. Ein Objekt oder eine Methode, die Sie möglicherweise polyfillieren müssen.
JMM

6
Abgesehen von den Kompatibilitätsproblemen kann Object.assign das ursprüngliche Objekt mutieren, was nützlich ist. verbreiten kann nicht.
Pstanton

2
Um den Kommentar von @ pstanton zu verdeutlichen, kann object.assign ein vorhandenes Zielobjekt ändern (Eigenschaften aus der Quelle überschreiben, während andere Eigenschaften intakt bleiben). es berührt nicht die Quelle - Objekt. Ich habe sein "Originalobjekt" zuerst als "Quellobjekt" gelesen und diese Notiz für alle anderen geschrieben, die sie ähnlich falsch interpretieren. :)
ToolmakerSteve

Antworten:


328

Dies ist nicht unbedingt erschöpfend.

Syntax verbreiten

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

Vorteile:

  • Wenn Sie Code für die Ausführung in Umgebungen ohne native Unterstützung erstellen, können Sie diese Syntax möglicherweise nur kompilieren (im Gegensatz zur Verwendung einer Polyfüllung). (Zum Beispiel mit Babel.)

  • Weniger ausführlich.

Nachteile:

  • Als diese Antwort ursprünglich geschrieben wurde, war dies ein Vorschlag , der nicht standardisiert war. Überlegen Sie bei der Verwendung von Vorschlägen, was Sie tun würden, wenn Sie jetzt Code damit schreiben und dieser auf dem Weg zur Standardisierung nicht standardisiert oder geändert wird. Dies wurde inzwischen in ES2018 standardisiert.

  • Wörtlich, nicht dynamisch.


Object.assign()

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

Vorteile:

  • Standardisiert.

  • Dynamisch. Beispiel:

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

Nachteile:

  • Ausführlicher.
  • Wenn Sie Code für die Ausführung in Umgebungen ohne native Unterstützung erstellen, müssen Sie eine Polyfüllung durchführen.

Dies ist das Commit, das mich wundern ließ.

Das hängt nicht direkt mit dem zusammen, was Sie fragen. Dieser Code wurde nicht verwendet Object.assign(), sondern Benutzercode ( object-assign), der dasselbe tut. Sie scheinen diesen Code mit Babel zu kompilieren (und ihn mit Webpack zu bündeln), worüber ich gesprochen habe: die Syntax, die Sie einfach kompilieren können. Sie zogen es anscheinend vor, dies object-assignals Abhängigkeit einbeziehen zu müssen, die in ihren Build einfließen würde.


15
Es könnte sich lohnen, unter Hinweis darauf , dass das Objekt Rest Spread 3 bis Stufe bewegt hat , wird so wahrscheinlich in Zukunft standardisiert werden twitter.com/sebmarkbage/status/781564713750573056
williaster

11
@JMM Ich bin mir nicht sicher, ob ich "ausführlicher" sehe. als Nachteil.
DiverseAndRemote.com

6
@Omar Nun, ich denke du hast gerade bewiesen, dass es eine Meinung ist :) Wenn jemand diesen Vorteil nicht erkennt, dann ja, wenn alle anderen gleich sind, können sie ihn einfach nutzen Object.assign(). Oder Sie können manuell über ein Array von Objekten und ihre eigenen Requisiten iterieren und sie manuell einem Ziel zuweisen und es noch ausführlicher machen: P
JMM

7
Wie @JMM erwähnt, ist es jetzt in der ES2018-Spezifikation node.green/#ES2018-features-object-rest-spread-properties
Sebastien H.

2
"Jedes Byte zählt" Das ist, was Minimierer / uglify für @yzorg
DiverseAndRemote.com

171

Als Referenzobjekt wird Rest / Spread in ECMAScript 2018 als Stufe 4 finalisiert. Den Vorschlag finden Sie hier .

Das Zurücksetzen und Verteilen von Objekten funktioniert größtenteils auf die gleiche Weise. Der Hauptunterschied besteht darin, dass Spread Eigenschaften definiert, während Object.assign () sie festlegt . Dies bedeutet, dass Object.assign () Setter auslöst.

Es ist zu beachten, dass Objektrest / Spread 1: 1 anders als Object.assign () zugeordnet ist und sich anders verhält als Array (iterable) Spread. Wenn Sie beispielsweise ein Array verteilen, werden Nullwerte verteilt. Bei Verwendung von Objektstreuung werden Nullwerte jedoch stillschweigend auf nichts verteilt.

Array (Iterable) Spread-Beispiel

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

Beispiel für die Objektverteilung

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

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

Dies steht im Einklang mit der Funktionsweise von Object.assign (). Beide schließen den Nullwert ohne Fehler aus.

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

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

10
Dies sollte die ausgewählte Antwort sein.
Evan Plaice

4
Dies sollte die Antwort sein ... es ist jetzt die Zukunft.
Zachary Abresch

1
Das ist die richtige Antwort. Der Hauptunterschied besteht darin Object.assign, Setter zu verwenden. 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
"Die Zukunft ist jetzt!" - George Allen Morgen ist es zu spät.
Ruffin

1
Die Demonstration, dass Null anders gehandhabt wird, ist "Äpfel und Orangen" - kein aussagekräftiger Vergleich. Im Array-Fall ist null ein Element des Arrays. Im Spread-Fall ist null das gesamte Objekt. Der richtige Vergleich wäre, wenn x eine Null-Eigenschaft hat : const x = {c: null};. In diesem Fall, AFAIK, würden wir ein Verhalten wie das Array sehen : //{a: 1, b: 2, c: null}.
ToolmakerSteve

39

Ich denke, ein großer Unterschied zwischen dem Spread-Operator und dem Object.assign, der in den aktuellen Antworten nicht erwähnt zu werden scheint, besteht darin, dass der Spread-Operator den Prototyp des Quellobjekts nicht auf das Zielobjekt kopiert. Wenn Sie einem Objekt Eigenschaften hinzufügen möchten und nicht ändern möchten, um welche Instanz es sich handelt, müssen Sie diese verwenden Object.assign. Das folgende Beispiel sollte dies demonstrieren:

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


"wird den Prototyp nicht intakt halten" - wird es sicherlich, da es nichts ändert. In Ihrem Beispiel handelt es errorExtendedUsingAssign === errorsich jedoch errorExtendedUsingSpreadum ein neues Objekt (und der Prototyp wurde nicht kopiert).
Maaartinus

2
@maaartinus Du hast recht, ich habe das wahrscheinlich schlecht formuliert. Ich meinte, dass sich der Prototyp nicht auf dem kopierten Objekt befindet. Ich könnte das bearbeiten, um es klarer zu machen.
Sean Dawson

Ist das Folgende eine Möglichkeit, ein Objekt mit seiner Klasse "flach zu klonen"? let target = Object.create(source); Object.assign(target, source);
ToolmakerSteve

@ToolmakerSteve Ja, es werden alle "eigenen Eigenschaften" des Objekts kopiert, über die effektiv ein flacher Klon erstellt wird. Siehe: stackoverflow.com/questions/33692912/…
Sean Dawson

12

Wie andere bereits erwähnt haben, Object.assign()erfordert in diesem Moment des Schreibens eine Polyfüllung, und die Objektverbreitung ...erfordert ein gewisses Transpiling (und möglicherweise auch eine Polyfüllung), um zu arbeiten.

Betrachten Sie diesen Code:

// 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);

Diese beiden erzeugen die gleiche Ausgabe.

Hier ist die Ausgabe von Babel an 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);

Das ist mein bisheriges Verständnis. Object.assign()ist eigentlich standardisiert, da als objektverbreitung ...noch nicht erfolgt. Das einzige Problem ist die Browserunterstützung für die ersteren und in Zukunft auch für die letzteren.

Spielen Sie hier mit dem Code

Hoffe das hilft.


1
Vielen Dank! Ihr Codebeispiel macht die Entscheidung für meinen Kontext wirklich einfach. Der Transpiler (Babel oder Typoskript) macht den Spread-Operator besser mit Browsern kompatibel, indem er eine Pollyfill in den Inline-Code einfügt. Just for Interesse ist die Typoskript transpiled Version praktisch die gleichen wie Babel: typescriptlang.org/play/...
Mark Whitfeld

2
hmm ... würden Ihre beiden Fälle nicht zu den gleichen Ergebnissen führen? Im ersten Fall kopieren Sie Eigenschaften von einem Objekt in ein anderes und im anderen Fall erstellen Sie ein neues Objekt. Object.assign gibt das Ziel zurück, sodass in Ihrem ersten Fall objAss und newObjAss identisch sind.
Kevin B

Das Hinzufügen eines neuen ersten Parameters von {}sollte die Inkonsistenz beheben.
Kevin B

11

Der Objektverbreitungsoperator (...) funktioniert in Browsern nicht, da er noch nicht Teil einer ES-Spezifikation ist, sondern nur ein Vorschlag. Die einzige Möglichkeit besteht darin, es mit Babel (oder ähnlichem) zu kompilieren.

Wie Sie sehen können, handelt es sich nur um syntaktischen Zucker über Object.assign ({}).

Soweit ich sehen kann, sind dies die wichtigen Unterschiede.

  • Object.assign funktioniert in den meisten Browsern (ohne zu kompilieren)
  • ... für Objekte ist nicht standardisiert
  • ... schützt Sie vor versehentlichem Mutieren des Objekts
  • ... wird Object.assign in Browsern ohne es polyfill
  • ... benötigt weniger Code, um dieselbe Idee auszudrücken

34
Es ist kein syntaktischer Zucker für Object.assign, da der Spread-Operator Ihnen immer ein neues Objekt gibt.
MaxArt

4
Eigentlich bin ich überrascht, dass andere Leute den Mutabilitätsunterschied nicht mehr betonen. Denken Sie an all die Entwicklerstunden, die beim Debuggen versehentlicher Mutationen mit Object.assign
deepelement

Dies wird jetzt in den meisten modernen Browsern (wie bei anderen ES6) unterstützt: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
akmalhakimi1991

11

Ich möchte den Status der ES-Funktion "Spread Object Merge" in Browsern und im Ökosystem über Tools zusammenfassen.

Spec

Browser: bald in Chrome, in SF, Firefox (Version 60, IIUC)

  • Browserunterstützung für in Chrome 60 gelieferte "Spread Properties" , einschließlich dieses Szenarios.
  • Die Unterstützung für dieses Szenario funktioniert NICHT in aktuellem Firefox (59), funktioniert jedoch in meiner Firefox Developer Edition. Ich glaube also, dass es in Firefox 60 ausgeliefert wird.
  • Safari: nicht getestet, aber Kangax sagt, dass es in Desktop Safari 11.1 funktioniert, aber nicht in SF 11
  • iOS Safari: nicht getestet, aber Kangax sagt, dass es in iOS 11.3 funktioniert, aber nicht in iOS 11
  • noch nicht in Edge

Werkzeuge: Knoten 8.7, TS 2.1

Links

Codebeispiel (dient gleichzeitig als Kompatibilitätstest)

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 }

Nochmals: Zum Zeitpunkt des Schreibens funktioniert dieses Beispiel ohne Transpilation in Chrome (60+), Firefox Developer Edition (Vorschau von Firefox 60) und Node (8.7+).

Warum antworten?

Ich schreibe dies 2,5 Jahre nach der ursprünglichen Frage. Aber ich hatte genau die gleiche Frage, und hier hat Google mich hingeschickt. Ich bin ein Sklave von SOs Mission, den langen Schwanz zu verbessern.

Da dies eine Erweiterung der "Array Spread" -Syntax ist, war es für mich sehr schwierig zu googeln und in Kompatibilitätstabellen schwer zu finden. Das nächste, das ich finden konnte, ist Kangax "Property Spread" , aber dieser Test enthält nicht zwei Spreads im selben Ausdruck (keine Zusammenführung). Auch der Name auf den Statusseiten für Vorschläge / Entwürfe / Browser verwendet alle "Eigenschaftsverteilung", aber es scheint mir, dass dies ein "erster Grundsatz" war, zu dem die Community nach den Vorschlägen zur Verwendung der Verbreitungssyntax für "Objektzusammenführung" gelangt ist. (Dies könnte erklären, warum es so schwierig ist, zu googeln.) Daher dokumentiere ich meine Ergebnisse hier, damit andere Links zu dieser bestimmten Funktion anzeigen, aktualisieren und kompilieren können. Ich hoffe es fängt an. Bitte helfen Sie dabei, die Nachricht von der Landung in der Spezifikation und in den Browsern zu verbreiten.

Zuletzt hätte ich diese Informationen als Kommentar hinzugefügt, aber ich konnte sie nicht bearbeiten, ohne die ursprüngliche Absicht der Autoren zu brechen. Insbesondere kann ich den Kommentar von @ ChillyPenguin nicht bearbeiten, ohne dass er seine Absicht verliert, @RichardSchulte zu korrigieren. Aber Jahre später stellte sich Richard als richtig heraus (meiner Meinung nach). Also schreibe ich stattdessen diese Antwort in der Hoffnung, dass sie irgendwann bei den alten Antworten an Bedeutung gewinnt (könnte Jahre dauern, aber genau darum geht es schließlich beim Long-Tail- Effekt).


3
Ihr Abschnitt "Warum antworten" wird wahrscheinlich nicht benötigt
LocustHorde

@LocustHorde Vielleicht könnte ich den 2. Absatz (warum dieses Thema so schwer zu googeln ist) in einen eigenen Abschnitt verschieben. Dann könnte der Rest in einen Kommentar passen.
Yzorg

8

HINWEIS: Spread ist NICHT nur syntaktischer Zucker um Object.assign. Sie arbeiten hinter den Kulissen sehr unterschiedlich.

Object.assign wendet Setter auf ein neues Objekt an, Spread nicht. Außerdem muss das Objekt iterierbar sein.

Kopieren Verwenden Sie diese Option, wenn Sie den Wert des Objekts in diesem Moment benötigen und nicht möchten, dass dieser Wert Änderungen widerspiegelt, die von anderen Eigentümern des Objekts vorgenommen wurden.

Verwenden Sie diese Option, um eine flache Kopie des Objekts zu erstellen. Es wird empfohlen, unveränderliche Eigenschaften immer zu kopieren. Da veränderbare Versionen an unveränderliche Eigenschaften übergeben werden können, stellt copy sicher, dass Sie immer mit einem unveränderlichen Objekt arbeiten

Assign Assign ist etwas anderes als das Kopieren. Assign generiert einen Setter, der den Wert direkt der Instanzvariablen zuweist, anstatt ihn zu kopieren oder beizubehalten. Wenn der Getter einer Zuweisungseigenschaft aufgerufen wird, wird ein Verweis auf die tatsächlichen Daten zurückgegeben.


3
Warum steht dort "Kopieren"? Was sind diese kühnen Überschriften. Ich habe das Gefühl, dass ich einen Kontext verpasst habe, als ich das gelesen habe ...
ADJenks

2

Andere Antworten sind alt, konnten keine gute Antwort bekommen.
Das folgende Beispiel bezieht sich auf Objektliterale und hilft, wie sich beide ergänzen können und wie sie sich nicht ergänzen können (daher Unterschied):

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

Einige weitere kleine Beispiele hier, auch für Array & Objekt:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax


1

Dies ist jetzt Teil von ES6, daher standardisiert und auch auf MDN dokumentiert: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

Es ist sehr bequem zu bedienen und macht neben der Objektzerstörung viel Sinn.

Der einzige verbleibende Vorteil, der oben aufgeführt ist, sind die dynamischen Funktionen von Object.assign (). Dies ist jedoch so einfach wie das Verteilen des Arrays innerhalb eines Literalobjekts. In der kompilierten Babel-Ausgabe wird genau das verwendet, was mit Object.assign () demonstriert wird.

Die richtige Antwort wäre also, die Objektverteilung zu verwenden, da sie jetzt standardisiert und weit verbreitet ist (siehe Reagieren, Reduzieren usw.), einfach zu verwenden ist und alle Funktionen von Object.assign () bietet.


2
Nein, es ist nicht Teil von ES6. Der von Ihnen angegebene Link bezieht sich nur auf die Verwendung des Spread-Operators auf Arrays . Die Verwendung des Spread-Operators für Objekte ist derzeit ein Vorschlag der Stufe 2 (dh Entwurf), wie in der Antwort von JMM erläutert.
ChillyPenguin

1
Ich glaube nicht, dass ich jemals eine Antwort gesehen habe, die ausschließlich auf falschen Informationen basiert. Selbst ein Jahr später ist dies nicht Teil der ES-Spezifikation und wird in den meisten Umgebungen nicht unterstützt.
3ozän

Chilly und 3o erwiesen sich als falsch, Richard richtig. Browserunterstützung und Toolunterstützung landen alle, aber es dauerte 1,5 Jahre nach Richards Antwort. Siehe meine neue Antwort für eine Zusammenfassung der Unterstützung vom März 2018.
Yzorg

0

Ich möchte dieses einfache Beispiel hinzufügen, wenn Sie Object.assign verwenden müssen.

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!

Es kann nicht klar sein, wenn Sie JavaScript verwenden. Mit TypeScript ist es jedoch einfacher, eine Instanz einer Klasse zu erstellen

const spread: SomeClass = {...new SomeClass()} // Error
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.