JavaScript: Jak przekazać obiekt przez wartość?


96
  • Podczas przekazywania obiektów jako parametrów, JavaScript przekazuje je przez odniesienie i utrudnia tworzenie lokalnych kopii obiektów.

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

    obędzie mieć .fooi .bar.

  • Można to obejść przez klonowanie; prosty przykład:

    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)
    

    onie będzie mieć .foolub .bar.


Pytanie

  1. Czy istnieje lepszy sposób przekazywania obiektów według wartości, inny niż tworzenie lokalnej kopii / klonu?

3
Jaki jest Twój przypadek użycia tego?
jondavidjohn,

2
Zabawa w programowanie. Sprawdzanie, czy nowe silniki JS rozwiązały ten problem (technicznie rzecz biorąc, przekazuje referencję według wartości), ale głównie dla zabawy.
vol7ron,

1
Zobacz Czy JavaScript jest językiem przekazującym, czy przekazującym wartość? - JavaScript nie przechodzi przez odwołanie. Podobnie jak Java, podczas przekazywania obiektów do funkcji przekazuje wartość, ale wartość jest referencją. Zobacz także Czy Java jest przekazywana przez odniesienie? .
Richard JP Le Guen

1
O ile wiem, nie można przekazywać obiektów według wartości. Nawet gdyby tak było, w efekcie wykonywałoby to klonowanie, o którym mówisz powyżej, więc nie widzę nic do zyskania. Poza być może zapisaniem 3 linii kodu.
Thor84no,

1
@ vol7ron Tak, rozwiązali ten problem, poprawnie implementując charakterystykę projektu języka.
jondavidjohn

Odpowiedzi:


61

Nie całkiem.

W zależności od tego, czego faktycznie potrzebujesz, jedną z możliwości może być ustawienie ojako prototyp nowego obiektu.

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

alert( o.foo ); // undefined

Więc żadne właściwości, do których dodasz obj, nie zostaną dodane o. Wszelkie właściwości dodane do właściwości objo tej samej nazwie, co właściwość w programie, obędą cieniały owłaściwość.

Oczywiście wszelkie właściwości dodane do obędą dostępne z poziomu, objjeśli nie są zasłonięte, a wszystkie obiekty, które mają ow łańcuchu prototypów, zobaczą te same aktualizacje o.

Ponadto, jeśli objma właściwość odwołującą się do innego obiektu, takiego jak Array, musisz upewnić się, że obiekt ten jest cieniowany przed dodaniem elementów do obiektu, w przeciwnym razie te elementy zostaną dodane obji będą współdzielone przez wszystkie obiekty, które mają objw łańcuchu prototypów.

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

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

})(o);

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

Tutaj możesz zobaczyć, że ponieważ nie przesłałeś Array at bazon oz bazwłaściwością włączoną obj, o.bazArray zostanie zmodyfikowany.

Zamiast tego musisz najpierw go prześledzić:

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

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

})(o);

alert( o.baz[0] );  // undefined

3
+1, Object.createw mojej odpowiedzi też chciałem zasugerować , ale nie chciałem naciągać się wyjaśnianiem płytkości kopii ;-)
Andy E

+1, zapomniałem o tym. Próbowałem var obj = new Object(x). Dla mnie, pomyślałbym, że to new Object(x)będzie działać Object.create(x)domyślnie - interesujące
vol7ron

1
@ vol7ron: Tak, new Object(x)po prostu wypluwa z powrotem ten sam obiekt (jak pewnie zauważyłeś) . Byłoby miło, gdyby istniał rodzimy sposób klonowania. Nie jestem pewien, czy coś jest w przygotowaniu.
user113716

45

Sprawdź tę odpowiedź https://stackoverflow.com/a/5344074/746491 .

Krótko mówiąc, JSON.parse(JSON.stringify(obj))jest to szybki sposób na skopiowanie obiektów, jeśli można je serializować do json.


2
Staram się tego unikać, ponieważ obiekty nie zawsze mogą być serializowane, a ponieważ w przeglądarkach w średnim wieku, takich jak IE7 / 8, JSONnie jest dołączony i musi być zdefiniowany - równie dobrze może być bardziej opisowy z nazwą taką jak Clone. Podejrzewam jednak, że moja opinia może się zmienić w przyszłości, ponieważ JSON jest silniej zintegrowany z oprogramowaniem innym niż przeglądarki, takim jak bazy danych i IDE
vol7ron

1
Jeśli nie ma funkcji i obiektów daty, rozwiązanie JSON.parse wydaje się najlepsze. stackoverflow.com/questions/122102/…
Herr_Hansen

Naprawdę?!? Serializować do JSON tylko po to, aby utworzyć kopię obiektu? Jeszcze jeden powód, by nienawidzić tego absurdalnego języka.
RyanNerd

1
Serializacja JSON to naprawdę szybki sposób na jego skopiowanie? Zgodnie z dokumentacją NodeJS manipulacja JSON jest zadaniem najbardziej obciążającym procesor. nodejs.org/en/docs/guides/dont-block-the-event-loop/…
harshad

38

Oto funkcja klonowania, która wykona głęboką kopię obiektu:

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

Teraz możesz użyć w ten sposób:

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

3
To była głęboka odpowiedź.
Nathan

Piękny! Najlepsza odpowiedź IMO.
ganders

23

Posługiwać się Object.assign()

Przykład:

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

Bardziej przejrzysty przykład, który robi to samo:

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


4
Lepiej:var b = Object.assign({}, a);
Bergi

Zgoda. Odpowiednio aktualizuję odpowiedź. Ponadto nie każda przeglądarka obsługuje metodę assign (). W zależności od wymagań dotyczących kompatybilności, może być wymagane użycie wypełniacza, takiego jak ten na MDN.
Brandon

3
@Brandon, w samym linku, który podałeś, zawiera ostrzeżenie przed głębokim klonowaniem: „W przypadku głębokiego klonowania musimy użyć innych alternatyw, ponieważ Object. assign () kopiuje wartości właściwości. Jeśli wartość źródłowa jest odniesieniem do obiektu, kopiuje tylko tę wartość odniesienia. "
pwilcox

2
To działa, ale pamiętaj, że nie wykonuje głębokiej kopii, żadne obiekty zawarte w środku azostaną skopiowane jako odniesienie.
Stoinov

16

Jesteś trochę zdezorientowany, jak działają obiekty w JavaScript. Odniesieniem do obiektu jest wartość zmiennej. Nie ma wartości nieserializowanej. Podczas tworzenia obiektu jego struktura jest przechowywana w pamięci, a zmienna, do której został przypisany, zawiera odniesienie do tej struktury.

Nawet jeśli to, o co pytasz, zostało dostarczone w jakimś łatwym, rodzimym języku, technicznie nadal byłoby to klonowanie.

JavaScript jest tak naprawdę wartością przekazaną ... po prostu przekazana wartość może być odniesieniem do czegoś.


2
Hej AndyE! Nie jestem tym zdezorientowany, miałem nadzieję, że JS domyślnie wykona klonowanie. Istnieją duże nieefektywności w samodzielnym klonowaniu, więc jeśli silnik to zrobił, jestem pewien, że byłoby lepiej. Wątpię jednak, by potrzeba / zapotrzebowanie przewyższały korzyści.
vol7ron

13
W JS, gdy ludzie mówią przez odniesienie, a nie przez wartość, mają na myśli, że wartość jest raczej wskaźnikiem niż duplikatem. To dość ugruntowany żargon.
Erik Reppen

4
@Erik: Moim skromnym zdaniem uważam, że lepiej jest to zdefiniować jaśniej, aby nie mylić nowicjuszy z językiem. Tylko dlatego, że coś jest dobrze ugruntowane, nie oznacza, że ​​jest odpowiednie lub nawet poprawne technicznie (ponieważ nie jest). Ludzie mówią „przez odniesienie”, ponieważ łatwiej im to powiedzieć niż wyjaśnić, jak to naprawdę działa.
Andy E

6
Wartość jest odniesieniem do miejsca w pamięci. Ostatecznie nadal działasz na tym samym elemencie w pamięci, zamiast pracować z kopią. W jaki sposób uczynienie tego rozróżnienia niejednoznacznym ma pomóc nowo przybyłym? Tak jest to wyjaśnione w wielu książkach technicznych, w tym autorytatywnym przewodniku O'Reilly, który jest obecnie piątym (OT: i bardzo słabo zaktualizowanym) wydaniem.
Erik Reppen

1
@AndyE Przeczytałem trochę więcej, aby zobaczyć, o co tyle zamieszania. IMO, to jest trochę jak kwestia tego, czy JS ma „prawdziwe” OOP, czy też możemy właściwie nazwać coś „metodą” w porównaniu z innymi językami. Właściwie nie jestem pewien, czy możliwe byłoby traktowanie vars jako zapisywania bezpośrednio do tego samego obiektu w pamięci, zamiast nadpisywania jego skopiowanego wskaźnika w języku opartym na domknięciach z rozgałęzianiem działania operatora przypisania zabijającego wydajność. Jedyne rzeczywiste rozróżnienie między tym, co mamy na myśli w JS, a innymi językami, dotyczy tego, co się dzieje, gdy używasz operatora przypisania.
Erik Reppen

15

Użyj tego

x = Object.create(x1);

xi x1będą dwoma różnymi obiektami, zmiana xnie ulegnie zmianiex1


Skończyło się na korzystaniu z Object. assign (). W przypadku prostego przypadku testowego .create () działa, ale w przypadku bardziej złożonych (których nie mogę jeszcze znaleźć wzorca), nadal odwołuje się do tego samego adresu.
Coisox

Nie zmieni się to tylko wtedy, gdy wartości obiektu nie są przechowywane przez odniesienie, tj. Są to typy danych wstępnych, takie jak liczba lub wartość logiczna. Na przykład rozważ poniższy kod a = {x: [12], y: []}; b = Object.create (a); bxpush (12); console.log (topór);
Jjagwe Dennis

8

JavaScript zawsze przekazuje wartość. W tym przypadku przekazuje kopię referencji odo funkcji anonimowej. Kod używa kopii odwołania, ale mutuje pojedynczy obiekt. Nie ma sposobu, aby javascript przeszedł przez cokolwiek innego niż wartość.

W tym przypadku to, co chcesz, to przekazanie kopii podstawowego obiektu. Klonowanie obiektu jest jedynym rozwiązaniem. Twoja metoda klonowania wymaga jednak nieco aktualizacji

function ShallowCopy(o) {
  var copy = Object.create(o);
  for (prop in o) {
    if (o.hasOwnProperty(prop)) {
      copy[prop] = o[prop];
    }
  }
  return copy;
}

1
+1 Dla „zawsze przechodzi przez wartość”, chociaż wolę używać wywołania przez współdzielenie obiektu (w przeciwieństwie do „Wywołanie według wartości [odniesienia]”) i promować „wartość” jako „sam obiekt” (z uwagą, że obiekt nie jest kopiowany / klonowany / duplikowany), ponieważ odniesienia są tylko szczegółem implementacji i nie są ujawniane w JavaScript (ani bezpośrednio wspomniane w specyfikacji!).

Miałem problem z dodawaniem właściwości do obiektu, który otrzymuję z połączenia zdalnego (pomyślałem, że może to być problem polegający na tym, że nie mogę dodać żadnej właściwości do tego obiektu), więc wypróbowałem tę funkcję shallowcopy i otrzymałem ten błąd - Prototyp obiektu może być tylko Object lub null w var copy = Object.create (o); jakikolwiek pomysł, jak rozwiązać mój problem
Harshit Laddha,

Czy to nie jest głęboka kopia?
River Tam

@RiverTam Nie, to nie jest głęboka kopia, ponieważ nie klonuje obiektów w obiektach. Możesz jednak łatwo uczynić go głęboką kopią, po prostu zmuszając go do rekurencyjnego wywoływania siebie na każdym z podobiektów.
ABPerson

7

Mając na uwadze użytkowników jQuery, istnieje również sposób na zrobienie tego w prosty sposób przy użyciu frameworka. Jeszcze jeden sposób, w jaki jQuery sprawia, że ​​nasze życie jest trochę łatwiejsze.

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

Bibliografia :


Ach - odkryłeś to stare pytanie :) Myślę, że pierwotne pytanie było bardziej eksploracją języka JS. Wygląda na to, że nawet moja terminologia była wtedy słaba, ponieważ mój przykład był raczej głęboką kopią niż klonem . Wygląda jednak na to, że obiekty / skróty można przekazywać tylko przez odniesienie, co jest powszechne w językach skryptowych
vol7ron

1
:) Przeszedłem przez to szukając dokładnej funkcjonalności. Miałem obiekt danych, który musiałem skopiować do edycji przed przesłaniem, zachowując oryginalny obiekt. Wolę natywne rozwiązanie JS i będę go używać w mojej własnej bibliotece bazowej, ale rozwiązanie jQuery jest moją tymczasową poprawką. Przyjęta odpowiedź jest fantastyczna. : D
Brett Weber

6

W rzeczywistości JavaScript jest zawsze przekazywany według wartości . Ale ponieważ odniesienia do obiektów są wartościami , obiekty będą zachowywać się tak, jakby były przekazywane przez odniesienie .

Aby obejść ten problem , określ obiekt na łańcuchy i przeanalizuj go z powrotem, oba przy użyciu formatu JSON. Zobacz przykład kodu poniżej:

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

2
Tak, jest to opcja prostej konwersji / ponownej konwersji. Ogólnie praca z łańcuchami jest bardzo nieefektywna; chociaż od jakiegoś czasu nie oceniałem wydajności analizowania JSON. Pamiętaj, że chociaż działa to w przypadku prostych obiektów, obiekty, które zawierają funkcje jako wartości, utracą zawartość.
vol7ron

Właściwie przeprowadziłem testy porównawcze i widziałem, jak robią to inni, a ze względu na wydajność silnika javascripts json ta metoda jest naprawdę jednym z najszybszych sposobów, w jaki można to zrobić bez konieczności wprowadzania dużej biblioteki, pisania dużej ilości kodu lub bez robienia tego prawdziwa głęboka kopia.
tedivm

4

use obj2 = {... obj1} Teraz oba obiekty mają tę samą wartość, a inne odniesienia


2
Nowość w javascript tutaj. Operator spreadu tworzy nową wartość zamiast kopiowania przez odniesienie? Dlaczego więc to odrzucono?
jo3birdtalk

2
Na pierwszy rzut oka mi się to udaje. Ale odkrywam, że dotyczy to tylko pierwszej warstwy zmiennej. Na przykład oblicza, kiedy let a = {value: "old"} let b = [...a] b.value = "new"i będzie {value: "old"}, b będzie {value: "new"}. Ale nie działa, kiedy let a = [{value: "old"}] let b = [...a] b[0].value = "new"i będzie [{value: "new"}], b będzie [{value: "new"}].
Brady Huang

tak, robi płytką kopię. Jeśli masz zagnieżdżony obiekt, musisz wykonać głęboką kopię. Dla uproszczenia możesz użyć funkcji clone i deepClone lodash
Geetanshu Gulati

3

Musiałem skopiować obiekt według wartości (nie odniesienia) i ta strona była pomocna:

Jaki jest najbardziej efektywny sposób głębokiego klonowania obiektu w JavaScript? . W szczególności sklonowanie obiektu za pomocą następującego kodu autorstwa Johna Resiga:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Pytanie dotyczy JavaScript, a nie biblioteki jQuery.
j08691

1
To prawda, że ​​dla ludzi, którzy z jakiegokolwiek powodu nie mogą używać Jquery, nie pomoże im to. Jednakże, ponieważ Jquery jest biblioteką javascript, myślę, że nadal pomoże większości użytkownikom javascript.
BeepBop

Nie pomoże to żadnemu z programistów próbujących to zrobić na serwerze. Więc nie. To nie jest prawidłowa odpowiedź.
Tomasz Gałkowski

3

Ze składnią ES6:

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


3
Bardzo dobra wzmianka @galkowskit. Największym problemem (lepiej: rzecz do zapamiętania ) Object.assignjest to, że nie wykonuje głębokiej kopii.
vol7ron

1

Kiedy się do tego sprowadzisz, jest to po prostu fantazyjny, zbyt skomplikowany serwer proxy, ale może Catch-All Proxies mógłby to zrobić?

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, trochę się spóźniłem z odpowiedzią :) ale wydaje mi się, że nigdy tego nie zrobiłam z kilku powodów: szukałem cukru syntaktycznego, który dawał JS możliwość przekazywania obiektów na różne sposoby; twoja odpowiedź wyglądała na długą i odłożyłem słuchawkę na używanie przez ciebie „proxy”. 3 lata później i nadal nie mogę się doczekać użytkowania. Być może nie poświęcałem wystarczającej uwagi na zajęciach, ale podobnie jak w przypadku sieci, pomyślałem, że proxy w CS mają działać jako pośrednicy. Być może jest również tutaj i nie myślę poprawnie o tym, między czym pośredniczy.
vol7ron

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.