Kopieren Sie den Wert einer Variablen in eine andere

100

Ich habe eine Variable, deren Wert ein JSON-Objekt hat. Ich ordne diese Variable direkt einer anderen Variablen zu, damit sie denselben Wert haben. So funktioniert es:

var a = $('#some_hidden_var').val(),
    b = a;

Dies funktioniert und beide haben den gleichen Wert. Ich verwende einen mousemoveEreignishandler, um bmeine App zu aktualisieren . Bei einem Klick auf eine Schaltfläche möchte ich zum bursprünglichen Wert zurückkehren, dh zum Wert, der in gespeichert ist a.

$('#revert').on('click', function(e){
    b = a;
});

Wenn ich danach denselben mousemoveEreignishandler verwende, werden beide aund aktualisiertb , wenn zuvor, nur bwie erwartet aktualisiert .

Ich bin über dieses Problem ratlos! Was ist hier falsch?

Rutwick Gangurde
quelle
Bitte zeigen Sie Ihren Mousemove-Handler. Angesichts der Tatsache, dass dies festgelegt awurde, gehe .val()ich davon aus, dass es sich um JSON (eine Zeichenfolge) und nicht um ein Objekt handelt - stimmt das? Verwenden Sie JSON.parse(a)irgendwann, um ein tatsächliches Objekt zu erhalten?
nnnnnn
Ja, es ist eine Zeichenfolge, aber ich verwende sie $.parseJSON, um sie in ein Objekt zu konvertieren. Struktur : { 'key': {...}, 'key': {...}, ...}. Leider kann hier kein Code gepostet werden, der an meinem Arbeitsplatz nicht erlaubt ist!
Rutwick Gangurde
1
Ist aalso ein Objekt?
Armand
1
Sieht aus wie ein Scoping-Problem. Ist aeine globale Variable?
msturdy
2
Der angezeigte Code enthält nur Zeichenfolgen. Wenn Sie über Objekte sprechen, können sich mehrere Variablen auf dasselbe Objekt beziehen, sodass das Objekt über eine der Variablen mutiert werden kann. Daher $.parseJSON()ist es schwer zu sagen, wo das Problem liegt, ohne mehr von dem Code zu sehen, der die Variablen manipuliert (wo kommt er hinein?). In Bezug auf Ihre Arbeitsplatzregeln müssen Sie Ihren tatsächlichen Code nicht vollständig veröffentlichen. Sie müssen lediglich ein kürzeres und allgemeineres Beispiel erstellen, das das Problem veranschaulicht (und im Idealfall einen Link zu einer Live-Demo auf jsfiddle.net enthalten ). .
nnnnnn

Antworten:

198

Es ist wichtig zu verstehen, was der =Operator in JavaScript tut und was nicht.

Der =Betreiber erstellt keine Kopie der Daten.

Der =Bediener erstellt einen neuen Verweis auf dieselben Daten.

Nachdem Sie Ihren ursprünglichen Code ausgeführt haben:

var a = $('#some_hidden_var').val(),
    b = a;

aund bsind jetzt zwei verschiedene Namen für das gleiche Objekt .

Jede Änderung, die Sie am Inhalt dieses Objekts vornehmen, wird identisch angezeigt, unabhängig davon, ob Sie über die aVariable oder die referenzierenb Variable darauf . Sie sind das gleiche Objekt.

Wenn Sie später versuchen, mit diesem Code bzum ursprünglichen aObjekt "zurückzukehren" :

b = a;

Der Code macht eigentlich gar nichts , weil aund bgenau das gleiche sind. Der Code ist der gleiche, als hätten Sie geschrieben:

b = b;

was offensichtlich nichts tun wird.

Warum funktioniert Ihr neuer Code?

b = { key1: a.key1, key2: a.key2 };

Hier erstellen Sie ein brandneues Objekt mit dem {...}Objektliteral. Dieses neue Objekt ist nicht dasselbe wie Ihr altes Objekt. Sie stellen jetzt einb als Referenz auf dieses neue Objekt, das macht, was Sie wollen.

Um ein beliebiges Objekt zu verarbeiten, können Sie eine Objektklonfunktion wie die in Armands Antwort aufgeführte verwenden oder, da Sie jQuery verwenden, einfach die $.extend()Funktion verwenden . Diese Funktion erstellt entweder eine flache oder eine tiefe Kopie eines Objekts. (Verwechseln Sie dies nicht mit der $().clone()Methode zum Kopieren von DOM-Elementen, nicht von Objekten.)

Für eine flache Kopie:

b = $.extend( {}, a );

Oder eine tiefe Kopie:

b = $.extend( true, {}, a );

Was ist der Unterschied zwischen einer flachen und einer tiefen Kopie? Eine flache Kopie ähnelt Ihrem Code, der ein neues Objekt mit einem Objektliteral erstellt. Es wird ein neues Objekt der obersten Ebene erstellt, das Verweise auf dieselben Eigenschaften wie das ursprüngliche Objekt enthält.

Wenn Ihr Objekt nur primitive Typen wie Zahlen und Zeichenfolgen enthält, tun eine tiefe und eine flache Kopie genau dasselbe. Wenn Ihr Objekt jedoch andere darin verschachtelte Objekte oder Arrays enthält, kopiert eine flache Kopie diese verschachtelten Objekte nicht, sondern erstellt lediglich Verweise auf sie. Sie könnten also das gleiche Problem mit verschachtelten Objekten haben wie mit Ihrem Objekt der obersten Ebene. Zum Beispiel bei diesem Objekt:

var obj = {
    w: 123,
    x: {
        y: 456,
        z: 789
    }
};

Wenn Sie eine flache Kopie dieses Objekts erstellen, ist die xEigenschaft Ihres neuen Objekts dasselbe xObjekt wie das Original:

var copy = $.extend( {}, obj );
copy.w = 321;
copy.x.y = 654;

Jetzt sehen Ihre Objekte folgendermaßen aus:

// copy looks as expected
var copy = {
    w: 321,
    x: {
        y: 654,
        z: 789
    }
};

// But changing copy.x.y also changed obj.x.y!
var obj = {
    w: 123,  // changing copy.w didn't affect obj.w
    x: {
        y: 654,  // changing copy.x.y also changed obj.x.y
        z: 789
    }
};

Sie können dies mit einer tiefen Kopie vermeiden. Die tiefe Kopie wird in jedes verschachtelte Objekt und Array (und Datum in Armands Code) zurückgeführt, um Kopien dieser Objekte auf dieselbe Weise zu erstellen, wie sie eine Kopie des Objekts der obersten Ebene erstellt hat. Änderungen copy.x.ywürden sich also nicht auswirken obj.x.y.

Kurze Antwort: Im Zweifelsfall möchten Sie wahrscheinlich eine tiefe Kopie.

Michael Geary
quelle
Danke, das habe ich gesucht. Wie kann man das umgehen? In meiner aktuellen Situation war es einfach zu beheben, da ich nur 2 Eigenschaften hatte. Es könnte aber auch größere Objekte geben.
Rutwick Gangurde
1
Armands Antwort hat eine Objektklonfunktion, die den Trick macht. Da Sie jQuery verwenden, können Sie auch die integrierte $.extend()Funktion verwenden. Details oben. :-)
Michael Geary
Tolle Erklärung Michael!
Rutwick Gangurde
7
@MichaelGeary Es ist vielleicht ein Trottel, aber gilt die Regel nicht nur für Objekte und nicht für primitive Variablen? es könnte erwähnenswert in der Antwort sein
Jonathan dos Santos
JS Lösung dafür ist in Armands Antwort, wie Michael Geary sagte. =
AlexanderGriffin
57

Ich habe festgestellt, dass JSON funktioniert, aber achten Sie auf unsere Zirkelverweise

var newInstance = JSON.parse(JSON.stringify(firstInstance));
Kernowcode
quelle
2
Mir ist klar, dass dies etwas spät ist, aber gibt es ein Problem mit dieser Methode? Es scheint beim ersten Versuch erstaunlich zu funktionieren.
Mankind1023
2
Dies ist in fast jeder Situation großartig. Wenn Sie jedoch mit Daten arbeiten, die möglicherweise spezifisch sind, stürzt Ihr Programm ab. Einige Code zur Veranschaulichung: let a = {}; let b = {a:a}; a.b = b; JSON.stringify(a)gibt einen TypeError
schu34
Diese Lösung funktioniert hervorragend. Wie teuer ist dies jedoch während der Verarbeitung? Es scheint, dass dies durch doppeltes Negieren funktioniert.
Abel Callejo
29

Die Frage ist schon seit geraumer Zeit gelöst, aber für zukünftige Referenz ist eine mögliche Lösung

b = a.slice(0);

Seien Sie vorsichtig, dies funktioniert nur dann korrekt, wenn a ein nicht verschachteltes Array von Zahlen und Zeichenfolgen ist

Marcosh
quelle
23

newVariable = originalVariable.valueOf();

für Objekte, die Sie verwenden können, b = Object.assign({},a);

Kishor Patil
quelle
3
für Objekte, die Sie verwenden können, b = Object.assign ({}, a);
Kishor Patil
15

Der Grund dafür ist einfach. JavaScript verwendet Referenzen. Wenn Sie also zuweisen, weisen b = aSie einen Verweis zu , sodass Sie bbeim Aktualisieren aauch aktualisierenb

Ich habe dies bei Stackoverflow gefunden und werde helfen, solche Dinge in Zukunft zu verhindern, indem ich nur diese Methode aufrufe, wenn Sie eine tiefe Kopie eines Objekts erstellen möchten.

function clone(obj) {
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}
Armand
quelle
Vielen Dank! Ich habe diesen Beitrag gesehen, bevor ich eine neue Frage gestellt habe. Aber ich war mir nicht sicher, ob das in meinem Szenario helfen würde. Es stellt sich heraus, dass ich das brauche.
Rutwick Gangurde
Sollten nicht alle außer dem ersten if (obj instanceof x) { ... }anders sein, wenn?
Zac
@ Zac Nicht in diesem Fall. Jeder if-body (von Interesse) gibt einen Wert zurück. (Beendet die Funktion vor dem nächsten, wenn)
Bitterblue
8

Ich verstehe nicht, warum die Antworten so komplex sind. In Javascript werden Grundelemente (Zeichenfolgen, Zahlen usw.) als Wert übergeben und kopiert. Objekte, einschließlich Arrays, werden als Referenz übergeben. In jedem Fall ändert die Zuweisung eines neuen Wertes oder einer neuen Objektreferenz zu 'a' nichts an 'b'. Durch Ändern des Inhalts von 'a' wird jedoch der Inhalt von 'b' geändert.

var a = 'a'; var b = a; a = 'c'; // b === 'a'

var a = {a:'a'}; var b = a; a = {c:'c'}; // b === {a:'a'} and a = {c:'c'}

var a = {a:'a'}; var b = a; a.a = 'c'; // b.a === 'c' and a.a === 'c'

Fügen Sie eine der oben genannten Zeilen (einzeln) in einen Knoten oder eine Browser-Javascript-Konsole ein. Geben Sie dann eine beliebige Variable ein und die Konsole zeigt ihren Wert an.

Trenton D. Adams
quelle
4

Für Zeichenfolgen oder Eingabewerte können Sie einfach Folgendes verwenden:

var a = $('#some_hidden_var').val(),
b = a.substr(0);
Tim
quelle
Warum würden Sie ein Primitiv unterteilen? Weisen Sie es einfach zu, es kopiert die Zeichenfolge. Nur Objekte und Arrays (die Objekte sind) werden als Referenz übergeben.
Trenton D. Adams
2

Die meisten Antworten hier verwenden integrierte Methoden oder Bibliotheken / Frameworks. Diese einfache Methode sollte gut funktionieren:

function copy(x) {
    return JSON.parse( JSON.stringify(x) );
}

// Usage
var a = 'some';
var b = copy(a);
a += 'thing';

console.log(b); // "some"

var c = { x: 1 };
var d = copy(c);
c.x = 2;

console.log(d); // { x: 1 }
Lasse Brustad
quelle
Genius! Andere Methoden verwandeln alles in ein Wörterbuch, einschließlich der darin enthaltenen Arrays. Damit Object.assign({},a)
Tiki
0

Ich habe es vorerst selbst gelöst. Der ursprüngliche Wert hat nur 2 Untereigenschaften. Ich habe ein neues Objekt mit den Eigenschaften von reformiert aund es dann zugewiesen b. Jetzt wird mein Event-Handler nur noch aktualisiert bund mein Original ableibt unverändert.

var a = { key1: 'value1', key2: 'value2' },
    b = a;

$('#revert').on('click', function(e){
    //FAIL!
    b = a;

    //WIN
    b = { key1: a.key1, key2: a.key2 };
});

Das funktioniert gut. Ich habe keine einzige Zeile in meinem Code geändert, außer den oben genannten, und es funktioniert genau so, wie ich es wollte. Also, vertrau mir, nichts anderes wurde aktualisiert a.

Rutwick Gangurde
quelle
0

Eine Lösung für AngularJS :

$scope.targetObject = angular.copy($scope.sourceObject)
Tony Sepia
quelle