Skopiuj wartość zmiennej do innej

100

Mam zmienną, której wartością jest obiekt JSON. Bezpośrednio przypisuję tę zmienną do innej zmiennej, aby miały tę samą wartość. Tak to działa:

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

To działa i oba mają tę samą wartość. Używam programu mousemoveobsługi zdarzeń do aktualizacji bza pośrednictwem mojej aplikacji. Po kliknięciu przycisku chcę przywrócić bpierwotną wartość, czyli wartość przechowywaną w a.

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

Następnie, jeśli używam tego samego programu mousemoveobsługi zdarzeń, aktualizuje on oba, aa bwcześniej aktualizował tylko bzgodnie z oczekiwaniami.

Jestem zaskoczony tym problemem! Co tu jest nie tak?

Rutwick Gangurde
źródło
Proszę pokazać swój moduł obsługi myszy. Ponadto, biorąc pod uwagę, że azostał ustawiony z .val(), zakładam, że jest to JSON (ciąg znaków), a nie obiekt - czy to prawda? Czy używasz JSON.parse(a)kiedyś, aby uzyskać rzeczywisty obiekt?
nnnnnn
Tak, jest to ciąg znaków, ale używam go $.parseJSONdo konwersji na obiekt. Struktura: { 'key': {...}, 'key': {...}, ...}. Przepraszamy, nie mogę tutaj umieścić żadnego kodu, niedozwolone w moim miejscu pracy!
Rutwick Gangurde
1
więc to ajest obiekt?
Armand
1
Wygląda na to, że problem dotyczy zakresu… czy jest azmienną globalną?
msturdy,
2
Kod, który pokazałeś, zawiera tylko ciągi. Jeśli mówisz o obiektach, to wiele zmiennych może odnosić się do tego samego obiektu, a więc obiekt może zostać zmutowany przez dowolną ze zmiennych. Dlatego nie widząc więcej kodu, który manipuluje zmiennymi (skąd się to $.parseJSON()bierze?), Trudno powiedzieć, na czym polega problem. Jeśli chodzi o zasady dotyczące miejsca pracy, nie musisz publikować całego rzeczywistego kodu, po prostu wymyśl krótszy i bardziej ogólny przykład, który demonstruje problem (i najlepiej, aby zawierał link do demonstracji na żywo na jsfiddle.net ) .
nnnnnn

Odpowiedzi:

201

Ważne jest, aby zrozumieć, co robi =operator w JavaScript, a czego nie.

=Operator nie robi kopii danych.

=Operator tworzy nowe odniesienie do samego danych.

Po uruchomieniu oryginalnego kodu:

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

ai bsą teraz dwiema różnymi nazwami dla tego samego obiektu .

Wszelkie zmiany wprowadzone w zawartości tego obiektu będą widoczne identycznie, niezależnie od tego, czy odwołujesz się do niego za pośrednictwem azmiennej, czy bzmiennej. To ten sam obiekt.

Tak więc, gdy później spróbujesz „przywrócić” boryginalny aobiekt za pomocą tego kodu:

b = a;

Kod właściwie nic nie robi , ponieważ ai bsą dokładnie tym samym. Kod jest taki sam, jak gdybyś napisał:

b = b;

co oczywiście nic nie da.

Dlaczego twój nowy kod działa?

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

Tutaj tworzysz zupełnie nowy obiekt z {...}literałem obiektu. Ten nowy obiekt nie jest tym samym, co stary obiekt. Więc teraz ustawiasz bjako odniesienie do tego nowego obiektu, który robi to, co chcesz.

Aby obsłużyć dowolny obiekt, możesz użyć funkcji klonowania obiektu, takiej jak ta wymieniona w odpowiedzi Armanda, lub ponieważ używasz jQuery, po prostu użyj tej $.extend()funkcji . Ta funkcja utworzy płytką kopię lub głęboką kopię obiektu. (Nie myl tego z $().clone()metodą kopiowania elementów DOM, a nie obiektów).

W przypadku płytkiej kopii:

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

Lub głęboka kopia:

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

Jaka jest różnica między płytką kopią a głęboką kopią? Płytka kopia jest podobna do kodu, który tworzy nowy obiekt z literałem obiektu. Tworzy nowy obiekt najwyższego poziomu zawierający odniesienia do tych samych właściwości, co oryginalny obiekt.

Jeśli twój obiekt zawiera tylko prymitywne typy, takie jak liczby i łańcuchy, głęboka kopia i płytka kopia zrobią dokładnie to samo. Ale jeśli twój obiekt zawiera inne obiekty lub tablice zagnieżdżone w nim, to płytka kopia nie kopiuje tych zagnieżdżonych obiektów, a jedynie tworzy do nich odniesienia. Więc możesz mieć ten sam problem z zagnieżdżonymi obiektami, który miałeś z obiektem najwyższego poziomu. Na przykład biorąc pod uwagę ten obiekt:

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

Jeśli zrobisz płytką kopię tego obiektu, wówczas xwłaściwość nowego obiektu będzie taka sama xjak oryginalnego:

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

Teraz twoje obiekty będą wyglądać tak:

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

Możesz tego uniknąć dzięki głębokiej kopii. Głęboka kopia powtarza się do każdego zagnieżdżonego obiektu i tablicy (oraz daty w kodzie Armanda), aby utworzyć kopie tych obiektów w ten sam sposób, w jaki utworzyła kopię obiektu najwyższego poziomu. Więc zmiana copy.x.ynie wpłynie obj.x.y.

Krótka odpowiedź: w razie wątpliwości prawdopodobnie potrzebujesz głębokiej kopii.

Michael Geary
źródło
Dzięki, właśnie tego szukałem. Jakoś to obejść? W mojej obecnej sytuacji było to łatwe do naprawienia, ponieważ miałem tylko 2 właściwości. Ale mogą istnieć większe obiekty.
Rutwick Gangurde
1
Odpowiedź Armanda zawiera funkcję klonowania obiektów, która załatwi sprawę. A ponieważ używasz jQuery, możesz użyć wbudowanej $.extend()funkcji. Szczegóły powyżej. :-)
Michael Geary
Świetne wyjaśnienie Michael!
Rutwick Gangurde
7
@MichaelGeary To może być chwytanie dziur w dziury, ale czy reguła nie dotyczy tylko obiektów, a nie zmiennych pierwotnych? warto zauważyć w odpowiedzi
Jonathan dos Santos
Rozwiązanie JS jest w odpowiedzi Armands, jak powiedział Michael Geary. =
AlexanderGriffin
57

Odkryłem, że używanie JSON działa, ale uważaj na nasze odwołania cykliczne

var newInstance = JSON.parse(JSON.stringify(firstInstance));
kernowcode
źródło
2
Zdaję sobie sprawę, że to trochę za późno, ale czy jest jakiś problem z tą metodą? Wydaje się, że działa niesamowicie za pierwszym razem.
Mankind1023
2
Jest to świetne w prawie każdej sytuacji, jednak jeśli pracujesz z danymi, które mogą być cykliczne, spowoduje to zawieszenie programu. trochę kodu do zilustrowania: let a = {}; let b = {a:a}; a.b = b; JSON.stringify(a)da TypeError
schu34
To rozwiązanie działa świetnie. Jednak jak drogie jest to podczas przetwarzania? Wygląda na to, że działa to na zasadzie podwójnej negacji.
Abel Callejo
30

pytanie zostało już rozwiązane od dłuższego czasu, ale w przyszłości możliwe jest rozwiązanie

b = a.slice(0);

Uważaj, to działa poprawnie tylko wtedy, gdy a jest niezagnieżdżoną tablicą liczb i ciągów

marcosh
źródło
24

newVariable = originalVariable.valueOf();

dla przedmiotów, których możesz użyć, b = Object.assign({},a);

Kishor Patil
źródło
3
dla obiektów, których możesz użyć, b = Object. assign ({}, a);
Kishor Patil
15

Powód jest prosty. JavaScript używa referencji, więc kiedy przypisujesz b = a, przypisujesz odwołanie, bwięc podczas aktualizacji arównież aktualizujeszb

Znalazłem to na stackoverflow i pomogę zapobiec takim sytuacjom w przyszłości, po prostu wywołując tę ​​metodę, jeśli chcesz wykonać głęboką kopię obiektu.

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
źródło
Dzięki! Widziałem ten post, zanim opublikowałem nowe pytanie. Ale nie byłem pewien, czy to pomoże w moim scenariuszu. Okazuje się, że tego właśnie potrzebuję.
Rutwick Gangurde
Czy nie powinno if (obj instanceof x) { ... }być inaczej, jeśli tylko pierwszy ?
Zac
@Zac Nie w tym przypadku. Każdy element if-body (będący przedmiotem zainteresowania) zwraca wartość. (wychodzi z funkcji przed następnym if)
Bitterblue
8

Nie rozumiem, dlaczego odpowiedzi są tak złożone. W Javascript prymitywy (łańcuchy, liczby itp.) Są przekazywane przez wartość i kopiowane. Obiekty, w tym tablice, są przekazywane przez odwołanie. W każdym razie przypisanie nowej wartości lub odniesienia do obiektu do „a” nie zmieni „b”. Ale zmiana zawartości „a” zmieni zawartość „b”.

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'

Wklej dowolną z powyższych linii (pojedynczo) do węzła lub dowolnej konsoli javascript przeglądarki. Następnie wpisz dowolną zmienną, a konsola pokaże jej wartość.

Trenton D. Adams
źródło
4

W przypadku ciągów lub wartości wejściowych możesz po prostu użyć tego:

var a = $('#some_hidden_var').val(),
b = a.substr(0);
Tim
źródło
Dlaczego miałbyś podciągać prymityw? Po prostu przypisz, kopiuje ciąg. Tylko obiekty i tablice (które są obiektami) są przekazywane przez odwołanie.
Trenton D. Adams,
2

Większość odpowiedzi tutaj wykorzystuje wbudowane metody lub biblioteki / frameworki. Ta prosta metoda powinna działać dobrze:

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
źródło
Geniusz! Inne metody zamieniają wszystko w słownik, w tym tablice w nich. Dzięki temu klonuję obiekt, a zawartość pozostaje nienaruszona, w przeciwieństwie do tego:Object.assign({},a)
Tiki
0

Na razie sam to rozwiązałem. Oryginalna wartość ma tylko 2 właściwości podrzędne. Zreformowałem nowy obiekt z właściwościami od, aa następnie przypisałem go do b. Teraz mój program obsługi zdarzeń aktualizuje się tylko b, a mój oryginał apozostaje taki, jaki jest.

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

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

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

To działa dobrze. Nie zmieniłem ani jednej linii w moim kodzie poza powyższym i działa tak, jak chciałem. Więc zaufaj mi, nic innego się nie aktualizowało a.

Rutwick Gangurde
źródło
0

Rozwiązanie dla AngularJS :

$scope.targetObject = angular.copy($scope.sourceObject)
Tony Sepia
źródło