JavaScript: Wie übergebe ich ein Objekt nach Wert?

95
  • Wenn Sie Objekte als Parameter übergeben, übergibt JavaScript diese als Referenz und macht es schwierig, lokale Kopien der Objekte zu erstellen.

    var o = {};
    (function(x){
        var obj = x;
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)

    owird haben .foound .bar.

  • Es ist möglich, dies durch Klonen zu umgehen. einfaches Beispiel:

    var o = {};
    
    function Clone(x) {
       for(p in x)
       this[p] = (typeof(x[p]) == 'object')? new Clone(x[p]) : x[p];
    }
    
    (function(x){
        var obj = new Clone(x);
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)

    owird nicht haben .foooder .bar.


Frage

  1. Gibt es eine bessere Möglichkeit, Objekte nach Wert zu übergeben, als eine lokale Kopie / einen lokalen Klon zu erstellen?
vol7ron
quelle
3
Was ist Ihr Anwendungsfall dafür?
Jondavidjohn
2
Programmierspaß. Überprüfen, ob neue JS-Engines dies behoben haben (technisch gesehen wird die Referenz nach Wert übergeben), aber hauptsächlich zum Spaß.
Vol7ron
1
Siehe Ist JavaScript eine Referenz- oder Wertübergabesprache? - JavaScript wird nicht als Referenz übergeben. Wie bei Java werden beim Übergeben von Objekten an eine Funktion Werte übergeben, der Wert ist jedoch eine Referenz. Siehe auch Wird Java als Referenz übergeben? .
Richard JP Le Guen
1
Soweit mir bekannt ist, können Sie Objekte nicht nach Wert übergeben. Selbst wenn dies der Fall wäre, würde es tatsächlich das Klonen durchführen, auf das Sie sich oben beziehen. Es gibt also nichts zu gewinnen, was ich sehen kann. Außer vielleicht 3 Codezeilen speichern.
Thor84no
1
@ vol7ron Ja, sie haben es behoben, indem sie ein Sprachdesignmerkmal korrekt implementiert haben.
Jondavidjohn

Antworten:

61

Nicht wirklich.

Je nachdem, was Sie tatsächlich benötigen, besteht eine Möglichkeit darin, oden Prototyp eines neuen Objekts festzulegen.

var o = {};
(function(x){
    var obj = Object.create( x );
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o);

alert( o.foo ); // undefined

Eigenschaften, die Sie hinzufügen, objwerden also nicht hinzugefügt o. Alle Eigenschaften, die objmit demselben Eigenschaftsnamen wie eine Eigenschaft in hinzugefügt wurden, oschattieren dieo Eigenschaft.

Natürlich sind alle hinzugefügten Eigenschaften overfügbar, objwenn sie nicht schattiert sind, und alle Objekte oin der Prototypenkette werden mit denselben Aktualisierungen verseheno .

Wenn objeine Eigenschaft vorhanden ist, die auf ein anderes Objekt verweist, z. B. ein Array, müssen Sie sicherstellen, dass Sie dieses Objekt schattieren, bevor Sie dem Objekt Elemente hinzufügen. Andernfalls werden diese Elemente hinzugefügt objund von allen Objekten gemeinsam genutzt, die dies tun haben objin der Prototypkette.

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // 'new_value'

Hier können Sie sehen , dass , weil Sie nicht das Array an Schatten haben bazauf omit einer bazEigenschaft auf obj, dieo.baz wird Array geändert.

Stattdessen müssen Sie es zuerst beschatten:

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz = [];
    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // undefined
user113716
quelle
3
+1, wollte ich auch Object.createin meiner Antwort vorschlagen , aber ich wollte mich nicht darauf beschränken, die Oberflächlichkeit der Kopie zu erklären ;-)
Andy E
+1, das habe ich vergessen. Ich habe es versucht var obj = new Object(x). Für mich würde ich denken, dass new Object(x)dies Object.create(x)standardmäßig - interessant
vol7ron
1
@ vol7ron: Ja, new Object(x)spuckt einfach das gleiche Objekt zurück (wie du wahrscheinlich bemerkt hast) . Es wäre schön, wenn es eine native Möglichkeit zum Klonen gäbe. Ich bin mir nicht sicher, ob etwas in Arbeit ist.
user113716
43

Überprüfen Sie diese Antwort https://stackoverflow.com/a/5344074/746491 .

Kurz gesagt, JSON.parse(JSON.stringify(obj))ist eine schnelle Möglichkeit, Ihre Objekte zu kopieren, wenn Ihre Objekte in json serialisiert werden können.

svimre
quelle
2
Ich neige dazu, dies zu vermeiden, da die Objekte nicht immer serialisiert werden können und weil sie in Browsern mittleren Alters wie IE7 / 8 JSONnicht enthalten sind und definiert werden müssen - könnte genauso gut mit einem Namen wie beschrieben werden Clone. Ich vermute jedoch, dass sich meine Meinung in Zukunft ändern könnte, da JSON stärker in nicht browserbasierte Software wie Datenbanken und IDEs integriert ist
vol7ron
1
Wenn keine Funktionen und Datumsobjekte vorhanden sind, ist die Lösung JSON.parse am besten geeignet. stackoverflow.com/questions/122102/…
Herr_Hansen
Ja wirklich?!? Mit JSON serialisieren, nur um eine Objektkopie zu erstellen? Ein weiterer Grund, diese absurde Sprache zu hassen.
RyanNerd
1
JSON zu serialisieren ist wirklich eine schnelle Möglichkeit, es zu kopieren? Laut NodeJS-Dokumentation ist die JSON-Manipulation die CPU-intensivste Aufgabe. nodejs.org/en/docs/guides/dont-block-the-event-loop/…
Hardad
38

Hier ist die Klonfunktion, die eine tiefe Kopie des Objekts ausführt:

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;

    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);

    return temp;
}

Jetzt können Sie wie folgt verwenden:

(function(x){
    var obj = clone(x);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o)
Paul Varghese
quelle
3
Dies war eine tiefe Antwort.
Nathan
Wunderschönen! Beste Antwort IMO.
Ganders
23

Verwenden Object.assign()

Beispiel:

var a = {some: object};
var b = new Object;
Object.assign(b, a);
// b now equals a, but not by association.

Ein saubereres Beispiel, das dasselbe tut:

var a = {some: object};
var b = Object.assign({}, a);
// Once again, b now equals a.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Brandon
quelle
4
Besser:var b = Object.assign({}, a);
Bergi
Einverstanden. Antwort entsprechend aktualisieren. Außerdem unterstützt nicht jeder Browser die Methode assign (). Abhängig von Ihren Kompatibilitätsanforderungen kann die Verwendung einer Polyfüllung wie der auf dem MDN erforderlich sein.
Brandon
3
@Brandon gibt in dem von Ihnen angegebenen Link eine Warnung für das Deep Cloning aus: "Für das Deep Cloning müssen andere Alternativen verwendet werden, da Object.assign () Eigenschaftswerte kopiert. Wenn der Quellwert ein Verweis auf ein Objekt ist, es kopiert nur diesen Referenzwert. "
Pwilcox
2
Dies funktioniert, aber denken Sie daran, dass keine Tiefenkopie durchgeführt wird. Alle darin enthaltenen Objekte awerden als Referenz kopiert.
Stoinov
15

Sie sind etwas verwirrt darüber, wie Objekte in JavaScript funktionieren. Die Objektreferenz ist der Wert der Variablen. Es gibt keinen unserialisierten Wert. Wenn Sie ein Objekt erstellen, wird seine Struktur im Speicher gespeichert und die Variable, der es zugewiesen wurde, enthält einen Verweis auf diese Struktur.

Selbst wenn das, was Sie fragen, in einem einfachen, muttersprachlichen Konstrukt bereitgestellt würde, wäre es technisch immer noch Klonen.

JavaScript ist wirklich nur ein Wert, der übergeben wird. Es ist nur so, dass der übergebene Wert möglicherweise auf etwas verweist.

Andy E.
quelle
1
Hey AndyE! Das verwirrt mich nicht, ich hatte die Hoffnung, dass JS das Klonen standardmäßig durchführen würde. Es gibt große Ineffizienzen beim Selbstklonen. Wenn der Motor dies tun würde, wäre es sicher besser. Ich bezweifle jedoch, dass der Bedarf / die Nachfrage den Nutzen übersteigt.
Vol7ron
12
Wenn in JS Leute sagen, dass sie durch Referenz statt durch Wert sagen, meinen sie, dass der Wert eher ein Zeiger als ein Duplikat ist. Das ist ziemlich gut etablierter Jargon.
Erik Reppen
4
@Erik: Meiner bescheidenen Meinung nach ist es besser, es klarer zu definieren, um Neulinge nicht mit der Sprache zu verwirren. Nur weil etwas gut etabliert ist, heißt das nicht, dass es angemessen oder sogar technisch korrekt ist (weil es nicht ist). Die Leute sagen "durch Bezugnahme", weil es für sie einfacher ist, das zu sagen, als zu erklären, wie es wirklich funktioniert.
Andy E
6
Der Wert bezieht sich auf einen Speicherort im Speicher. Letztendlich agieren Sie immer noch auf dasselbe Element im Speicher, anstatt mit einer Kopie zu arbeiten. Wie soll diese mehrdeutige Unterscheidung Neulingen helfen? So wird es in vielen technischen Büchern erklärt, einschließlich O'Reillys Authoritative Guide, der jetzt in seiner fünften (OT: und sehr schwach aktualisierten) Ausgabe erscheint.
Erik Reppen
1
@AndyE Ich habe ein bisschen mehr gelesen, um zu sehen, worum es geht. IMO, dies ist eine Art Frage, ob JS eine "echte" OOP hat oder ob wir etwas im Vergleich zu anderen Sprachen als "Methode" bezeichnen können. Ich bin mir nicht sicher, ob es möglich wäre, vars so zu behandeln, dass sie direkt in dasselbe Objekt im Speicher schreiben, anstatt den kopierten Zeiger in einer schließungsbasierten Sprache mit leistungsmindernder Verzweigung des Verhaltens des Zuweisungsoperators zu überschreiben. Die einzige wirkliche Unterscheidung zwischen Verhalten in JS und anderen Sprachen besteht darin, was passiert, wenn Sie den Zuweisungsoperator verwenden.
Erik Reppen
14

Benutze das

x = Object.create(x1);

xund x1wird zwei verschiedene Objekte sein, Änderung in xwird sich nicht ändernx1

user5242529
quelle
Am Ende benutze ich Object.assign (). Für einen einfachen Testfall funktioniert .create (), aber für komplexere (die ich noch nicht finden kann) verweist es immer noch auf dieselbe Adresse.
Coisox
Es ändert sich nicht nur, wenn die Werte des Objekts nicht als Referenz gespeichert werden, dh es handelt sich um vorrangige Datentypen wie number oder boolean. Betrachten Sie beispielsweise den folgenden Code: a = {x: [12], y: []}; b = Object.create (a); bxpush (12); console.log (ax);
Jjagwe Dennis
8

Javascript wird immer als Wert übergeben. In diesem Fall wird eine Kopie der Referenz oan die anonyme Funktion übergeben. Der Code verwendet eine Kopie der Referenz, mutiert jedoch das einzelne Objekt. Es gibt keine Möglichkeit, Javascript an etwas anderem als dem Wert vorbeizulassen.

In diesem Fall möchten Sie eine Kopie des zugrunde liegenden Objekts übergeben. Das Klonen des Objekts ist der einzige Rückgriff. Ihre Klonmethode muss jedoch ein wenig aktualisiert werden

function ShallowCopy(o) {
  var copy = Object.create(o);
  for (prop in o) {
    if (o.hasOwnProperty(prop)) {
      copy[prop] = o[prop];
    }
  }
  return copy;
}
JaredPar
quelle
1
+1 Für "Immer nach Wert übergeben", obwohl ich Call by Object Sharing (im Gegensatz zu "Call by Value [der Referenz]") bevorzuge und "den Wert" auf "das Objekt selbst" hochstufte (mit dem Hinweis, dass Das Objekt wird nicht kopiert / geklont / dupliziert, da Referenzen nur ein Implementierungsdetail sind und nicht JavaScript ausgesetzt sind (oder direkt in der Spezifikation erwähnt werden!).
Ich hatte ein Problem beim Hinzufügen von Eigenschaften zu einem Objekt, das ich von einer Remoteverbindung erhalte (ich dachte, dies könnte das Problem sein, dass ich diesem Objekt keine Eigenschaft hinzufügen kann), also habe ich diese Flachkopierfunktion ausprobiert und diesen Fehler erhalten - Objektprototyp darf nur ein Objekt oder null bei var copy = Object.create (o) sein; jede Idee, wie ich mein Problem lösen kann
Harshit Laddha
Ist das nicht eine tiefe Kopie?
River Tam
@ RiverTam Nein, es ist keine tiefe Kopie, da die Objekte in den Objekten nicht geklont werden. Sie können es jedoch leicht zu einer tiefen Kopie machen, indem Sie es rekursiv auf jedem der Unterobjekte aufrufen lassen.
ABPerson
7

In Anbetracht der jQuery-Benutzer gibt es auch eine Möglichkeit, dies mithilfe des Frameworks auf einfache Weise zu tun. Nur eine andere Art, wie jQuery unser Leben ein wenig einfacher macht.

var oShallowCopy = jQuery.extend({}, o);
var oDeepCopy    = jQuery.extend(true, {}, o); 

Verweise :

Brett Weber
quelle
Ah - Sie haben diese alte Frage entdeckt :) Ich denke, die ursprüngliche Frage war eher eine Erforschung der JS-Sprache. Es scheint, dass sogar meine Terminologie damals schwach war, da mein Beispiel eher eine tiefe Kopie als ein Klon war . Aber es scheint, dass Objekte / Hashes nur als Referenz übergeben werden können, was in Skriptsprachen üblich ist
7ron
1
:) Kam darüber hinweg und suchte nach der genauen Funktionalität. Ich hatte ein Datenobjekt, das ich zum Bearbeiten kopieren musste, bevor ich es abschickte, während das ursprüngliche Objekt beibehalten wurde. Ich bevorzuge die native JS-Lösung und werde sie in meiner eigenen Basisbibliothek verwenden, aber die jQuery-Lösung ist meine vorübergehende Lösung. Die akzeptierte Antwort ist fantastisch. : D
Brett Weber
6

Tatsächlich wird Javascript immer als Wert übergeben . Da Objektreferenzen jedoch Werte sind , verhalten sich Objekte so, als würden sie als Referenz übergeben .

Um dies zu umgehen , stringifizieren Sie das Objekt und analysieren Sie es zurück, beide mit JSON. Siehe Codebeispiel unten:

var person = { Name: 'John', Age: '21', Gender: 'Male' };

var holder = JSON.stringify(person);
// value of holder is "{"Name":"John","Age":"21","Gender":"Male"}"
// note that holder is a new string object

var person_copy = JSON.parse(holder);
// value of person_copy is { Name: 'John', Age: '21', Gender: 'Male' };
// person and person_copy now have the same properties and data
// but are referencing two different objects
Abubakar Ahmad
quelle
2
Ja, dies ist eine Option für die einfache Konvertierung / Neukonvertierung. Im Allgemeinen ist das Arbeiten mit Zeichenfolgen sehr ineffizient. Allerdings habe ich die Leistung der JSON-Analyse seit einiger Zeit nicht mehr bewertet. Beachten Sie, dass Objekte, die Funktionen als Werte enthalten, zwar für einfache Objekte funktionieren, jedoch Inhalt verlieren.
Vol7ron
4

use obj2 = {... obj1} Jetzt haben beide Objekte den gleichen Wert und haben unterschiedliche Referenzen

Geetanshu Gulati
quelle
2
Neu in Javascript hier. Der Spread-Operator erstellt einen neuen Wert, anstatt ihn als Referenz zu kopieren. Warum wurde das abgelehnt?
Jo3birdtalk
2
Auf den ersten Blick klappt es für mich. Aber ich entdecke, dass es nur um die erste Ebene der Variablen geht. Zum Beispiel klappt es, wann let a = {value: "old"} let b = [...a] b.value = "new"und a sein wird {value: "old"}, b sein wird {value: "new"}. Aber es klappt nicht, wann let a = [{value: "old"}] let b = [...a] b[0].value = "new"und a sein wird [{value: "new"}], b sein wird [{value: "new"}].
Brady Huang
Ja, es kopiert flach. Wenn Sie ein verschachteltes Objekt haben, müssen Sie eine tiefe Kopie erstellen. Der Einfachheit halber können Sie die Klon- und DeepClone-Funktionen von lodash
Geetanshu Gulati
3

Ich musste ein Objekt nach Wert (nicht als Referenz) kopieren und fand diese Seite hilfreich:

Was ist der effizienteste Weg, um ein Objekt in JavaScript tief zu klonen? . Insbesondere das Klonen eines Objekts mit dem folgenden Code von John Resig:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
Anthony Haffey
quelle
Die Frage bezieht sich auf JavaScript, nicht auf die jQuery-Bibliothek.
j08691
1
Für Leute, die Jquery aus irgendeinem Grund nicht verwenden können, würde dies nicht helfen. Da Jquery jedoch eine Javascript-Bibliothek ist, denke ich, dass es den meisten Javascript-Benutzern weiterhin helfen wird.
Anthony Haffey
Es hilft keinem der Programmierer, dies auf dem Server zu versuchen. Also - nein. Es ist keine gültige Antwort.
Tomasz Gałkowski
3

Mit der ES6-Syntax:

let obj = Object.assign({}, o);

Tomasz Gałkowski
quelle
3
Sehr gute Erwähnung @galkowskit. Das größte Problem (besser: etwas zu beachten ) mit dem Object.assignist, dass es keine tiefe Kopie ausführt.
Vol7ron
1

Wenn Sie sich darauf beschränken, ist es nur ein ausgefallener, übermäßig komplizierter Proxy, aber vielleicht könnte Catch-All Proxies das tun?

var o = {
    a: 'a',
    b: 'b',
    func: function() { return 'func'; }
};

var proxy = Proxy.create(handlerMaker(o), o);

(function(x){
    var obj = x;
    console.log(x.a);
    console.log(x.b);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(proxy);

console.log(o.foo);

function handlerMaker(obj) {
  return {
   getOwnPropertyDescriptor: function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name);
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getPropertyDescriptor:  function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name); // not in ES5
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getOwnPropertyNames: function() {
     return Object.getOwnPropertyNames(obj);
   },
   getPropertyNames: function() {
     return Object.getPropertyNames(obj);                // not in ES5
   },
   defineProperty: function(name, desc) {

   },
   delete:       function(name) { return delete obj[name]; },   
   fix:          function() {}
  };
}
Richard JP Le Guen
quelle
Richard, ich bin etwas spät dran zu antworten :) aber ich glaube, ich habe es aus ein paar Gründen nie getan: Ich habe nach syntaktischem Zucker gesucht, der JS die Möglichkeit gab, Objekte auf unterschiedliche Weise zu übergeben; Ihre Antwort sah lang aus und ich wurde wegen Ihrer Verwendung von "Proxy" aufgehängt. 3 Jahre später bin ich immer noch aufgelegt. Vielleicht habe ich im Unterricht nicht genug Aufmerksamkeit geschenkt, aber wie beim Networking dachte ich, dass Proxies in CS als Vermittler fungieren sollen. Vielleicht ist es auch hier und ich denke nicht richtig darüber nach, zwischen was es vermittelt.
Vol7ron
0

Wenn Sie lodash oder verwenden npm, verwenden Sie die Zusammenführungsfunktion von lodash, um alle Eigenschaften des Objekts wie folgt in ein neues leeres Objekt zu kopieren:

var objectCopy = lodash.merge({}, originalObject);

https://lodash.com/docs#merge

https://www.npmjs.com/package/lodash.merge

Con Antonakos
quelle
Eine tiefe Kopie sollte auch Ereignisse enthalten. Ich bin mit lodash nicht allzu vertraut. Spiegelt sie Ereignisse für ein neues Objekt wider?
Vol7ron