JavaScript: come passare un oggetto per valore?


96
  • Quando si passano oggetti come parametri, JavaScript li passa per riferimento e rende difficile creare copie locali degli oggetti.

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

    oavrà .fooe .bar.

  • È possibile aggirare questo problema clonando; semplice esempio:

    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)

    onon avrà .fooo .bar.


Domanda

  1. C'è un modo migliore per passare gli oggetti in base al valore, diverso dalla creazione di una copia / clone locale?

3
Qual è il tuo caso d'uso per averne bisogno?
jondavidjohn

2
Divertimento nella programmazione. Vedere se i nuovi motori JS hanno affrontato questo problema (tecnicamente, sta passando il riferimento per valore), ma principalmente per divertimento.
vol7ron

1
Vedere JavaScript è un linguaggio di riferimento o di passaggio per valore? - JavaScript non passa per riferimento. Come Java, quando si passa oggetti a una funzione, passa per valore, ma il valore è un riferimento. Vedi anche Java è passato per riferimento? .
Richard JP Le Guen,

1
Per quanto ne so, non puoi passare oggetti per valore. Anche se ci fosse, in effetti farebbe la clonazione a cui ti riferisci sopra, quindi non c'è niente da guadagnare che posso vedere. Altro che forse salvare 3 righe di codice.
Thor84no,

1
@ vol7ron Sì, hanno risolto il problema implementando correttamente una caratteristica di progettazione del linguaggio.
jondavidjohn

Risposte:


61

Non proprio.

A seconda di ciò di cui hai effettivamente bisogno, una possibilità potrebbe essere quella di impostare ocome prototipo di un nuovo oggetto.

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

alert( o.foo ); // undefined

Pertanto, tutte le proprietà a cui aggiungi objnon verranno aggiunte o. Qualsiasi proprietà aggiunta a objcon lo stesso nome di proprietà di una proprietà in oombreggerà la oproprietà.

Ovviamente, tutte le proprietà aggiunte osaranno disponibili objse non sono ombreggiate e tutti gli oggetti che hanno onella catena del prototipo vedranno gli stessi aggiornamenti a o.

Inoltre, se objha una proprietà che fa riferimento a un altro oggetto, come un Array, dovrai assicurarti di ombreggiare quell'oggetto prima di aggiungere membri all'oggetto, altrimenti quei membri verranno aggiunti obje saranno condivisi tra tutti gli oggetti che avere objnella catena dei prototipi.

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

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

})(o);

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

Qui puoi vedere che poiché non hai ombreggiato l'Array bazsu ocon una bazproprietà attiva obj, l' o.bazArray viene modificato.

Quindi, invece, dovresti prima ombreggiarlo:

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, stavo per suggerire anche Object.createnella mia risposta, ma non volevo allungare per spiegare la superficialità della copia ;-)
Andy E,

+1, me ne sono dimenticato. Ci ho provato var obj = new Object(x). Per me, penserei che new Object(x)avrebbe eseguito Object.create(x)di default - interessante
vol7ron

1
@ vol7ron: Sì, new Object(x)sputa indietro lo stesso oggetto (come probabilmente hai notato) . Sarebbe bello se ci fosse un modo nativo per clonare. Non sono sicuro che ci sia qualcosa in cantiere.
user113716

43

Dai un'occhiata a questa risposta https://stackoverflow.com/a/5344074/746491 .

In breve, JSON.parse(JSON.stringify(obj))è un modo veloce per copiare i tuoi oggetti, se i tuoi oggetti possono essere serializzati in json.


2
Tendo ad evitarlo poiché gli oggetti non possono sempre essere serializzati e poiché nei browser di mezza età come IE7 / 8, JSONnon è incluso e deve essere definito - potrebbe anche essere più descrittivo con un nome come Clone. Tuttavia, sospetto che la mia opinione possa cambiare in futuro, poiché JSON è più fortemente integrato in software non basato su browser come database e IDE
vol7ron

1
Se non ci sono funzioni e oggetti data, la soluzione JSON.parse sembra essere la migliore. stackoverflow.com/questions/122102/...
Herr_Hansen

Veramente?!? Serializzare in JSON solo per creare una copia dell'oggetto? Ancora un motivo in più per odiare questo linguaggio assurdo.
RyanNerd

1
Serializzare JSON è davvero un modo veloce per copiarlo? Secondo la documentazione di NodeJS, la manipolazione di JSON è l'attività più intensiva della CPU. nodejs.org/en/docs/guides/dont-block-the-event-loop/…
harshad

38

Ecco la funzione clone che eseguirà una copia completa dell'oggetto:

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

Ora puoi usare in questo modo:

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

3
Questa è stata una risposta profonda.
Nathan

Bellissimo! Migliore risposta IMO.
ganders

23

Uso Object.assign()

Esempio:

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

Un esempio più pulito che fa la stessa cosa:

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
Meglio:var b = Object.assign({}, a);
Bergi

Concordato. Aggiornando la risposta di conseguenza. Inoltre, non tutti i browser supportano il metodo assign (). A seconda dei requisiti di compatibilità, potrebbe essere necessario utilizzare un polyfill come quello su MDN.
Brandon

3
@Brandon, proprio nel link che hai fornito, viene visualizzato un avviso per la clonazione profonda: "Per la clonazione profonda, dobbiamo utilizzare altre alternative perché Object.assign () copia i valori delle proprietà. Se il valore di origine è un riferimento a un oggetto, copia solo quel valore di riferimento. "
pwilcox

2
Funziona, ma tieni presente che non esegue la copia completa, tutti gli oggetti contenuti all'interno averranno copiati come riferimento.
Stoinov

15

Sei un po 'confuso su come funzionano gli oggetti in JavaScript. Il riferimento dell'oggetto è il valore della variabile. Non è presente alcun valore non serializzato. Quando crei un oggetto, la sua struttura viene archiviata in memoria e la variabile a cui è stata assegnata contiene un riferimento a quella struttura.

Anche se ciò che stai chiedendo fosse fornito in una sorta di semplice costrutto in linguaggio nativo, tecnicamente sarebbe comunque una clonazione.

JavaScript è in realtà solo un valore di passaggio ... è solo che il valore passato potrebbe essere un riferimento a qualcosa.


1
Hey AndyE! Non sono confuso da questo, speravo che JS eseguisse la clonazione per impostazione predefinita. Ci sono grosse inefficienze nell'auto clonazione, quindi se il motore lo facesse, sono sicuro che sarebbe meglio. Tuttavia, dubito che il bisogno / la domanda superi il vantaggio.
vol7ron

12
In JS, quando le persone dicono per riferimento invece che per valore, intendono che il valore è come un puntatore piuttosto che un duplicato. Questo è un gergo piuttosto consolidato.
Erik Reppen

4
@Erik: a mio modesto parere, penso che sia meglio definirlo in modo più chiaro per non confondere i nuovi arrivati ​​nella lingua. Solo perché qualcosa è consolidato non significa che sia appropriato o anche tecnicamente corretto (perché non lo è). La gente dice "per riferimento" perché è più facile per loro dirlo che spiegare come funziona davvero.
Andy E

6
Il valore è un riferimento a una posizione in memoria. Alla fine stai ancora agendo sullo stesso elemento in memoria invece di lavorare con una copia. In che modo rendere ambigua questa distinzione dovrebbe aiutare i nuovi arrivati? Questo è il modo in cui viene spiegato in molti libri di tecnologia, inclusa la Guida autorevole di O'Reilly, che è ora alla sua quinta edizione (OT: e molto debolmente aggiornata).
Erik Reppen

1
@ AndyE Ho letto un po 'di più per vedere di cosa si tratta. IMO, questo è un po 'come il problema se JS ha un OOP "vero" o se possiamo chiamare correttamente qualcosa un "metodo" rispetto ad altri linguaggi. In realtà non sono certo che sarebbe possibile trattare vars come scrivere direttamente sullo stesso oggetto in memoria piuttosto che sovrascrivere il suo puntatore copiato in un linguaggio basato sulla chiusura con ramificazioni che uccidono le prestazioni del comportamento dell'operatore di assegnazione. L'unica vera distinzione di comportamento in ciò che intendiamo in JS rispetto ad altri linguaggi è in ciò che accade quando usi l'operatore di assegnazione.
Erik Reppen

15

Usa questo

x = Object.create(x1);

xe x1saranno due oggetti diversi, il cambiamento xnon cambieràx1


Finisco per usare Object.assign (). Per un test case semplice, .create () funziona ma per più complessi (che non riesco ancora a trovare il modello), fa ancora riferimento allo stesso indirizzo.
Coisox

Non cambierà solo se i valori dell'oggetto non sono memorizzati per riferimento, cioè sono tipi di dati premitivi come numero o booleano. Ad esempio, considera il codice sotto a = {x: [12], y: []}; b = Object.create (a); bxpush (12); console.log (ascia);
Jjagwe Dennis

8

Javascript passa sempre per valore. In questo caso sta passando una copia del riferimento oalla funzione anonima. Il codice sta usando una copia del riferimento ma sta mutando il singolo oggetto. Non c'è modo di far passare javascript da qualcosa di diverso dal valore.

In questo caso quello che vuoi è passare una copia dell'oggetto sottostante. La clonazione dell'oggetto è l'unica risorsa. Il tuo metodo di clonazione necessita però di un piccolo aggiornamento

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

1
+1 Per "Passa sempre per valore" anche se preferisco usare Call by Object Sharing (al contrario di "Call by Value [del riferimento]") e promuovere "il valore" a "l'oggetto stesso" (con la nota che l'oggetto non viene copiato / clonato / duplicato) poiché i riferimenti sono solo un dettaglio di implementazione e non sono esposti a JavaScript (o menzionati direttamente nelle specifiche!).

Ho avuto un problema ad aggiungere proprietà a un oggetto che sto ricevendo da una connessione remota (ho pensato che potrebbe essere il problema che non sono in grado di aggiungere alcuna proprietà a quell'oggetto), quindi ho provato questa funzione di copia superficiale e ho ottenuto questo errore: Prototipo oggetto può essere solo un Object o null in var copy = Object.create (o); qualche idea su come risolvere il mio problema
Harshit Laddha

Non è una copia completa?
River Tam

@RiverTam No, non è una copia completa, perché non clona gli oggetti all'interno degli oggetti. Tuttavia, puoi facilmente renderlo una copia completa semplicemente facendo in modo che si richiami ricorsivamente su ciascuno dei sotto-oggetti.
ABPerson

7

Come considerazione per gli utenti di jQuery, c'è anche un modo per farlo in modo semplice usando il framework. Solo un altro modo in cui jQuery rende le nostre vite un po 'più facili.

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

Riferimenti :


Ah, hai scoperto questa vecchia domanda :) Penso che la domanda originale fosse più un'esplorazione del linguaggio JS. Sembra che anche la mia terminologia fosse debole allora poiché il mio esempio era di una copia profonda piuttosto che di un clone . Ma sembra che gli oggetti / hash possano essere passati solo per riferimento, il che è comune nei linguaggi di scripting
vol7ron

1
:) Mi sono imbattuto cercando la funzionalità esatta. Avevo un oggetto dati che dovevo copiare per la modifica prima di inviarlo mentre mantenevo l'oggetto originale. Preferisco la soluzione JS nativa e la userò nella mia libreria di base, ma la soluzione jQuery è la mia soluzione temporanea. La risposta accettata è fantastica. : D
Brett Weber

6

In realtà, Javascript viene sempre passato per valore . Ma poiché i riferimenti agli oggetti sono valori , gli oggetti si comporteranno come se fossero passati per riferimento .

Quindi, per aggirare questo problema , stringi l'oggetto e analizzalo di nuovo, entrambi utilizzando JSON. Vedi esempio di codice di seguito:

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
Sì, questa è un'opzione per una semplice conversione / riconversione. In genere, lavorare con le stringhe è molto inefficiente; tuttavia, non ho valutato le prestazioni dell'analisi JSON da un po 'di tempo. Tieni presente che mentre questo funziona per oggetti semplici, gli oggetti che contengono funzioni come valori, perderanno contenuto.
vol7ron

4

usa obj2 = {... obj1} Ora entrambi gli oggetti hanno lo stesso valore e un riferimento diverso


2
Nuovo in javascript qui. L'operatore Spread crea un nuovo valore invece di copiare per riferimento? Allora perché questo è stato downvoted?
jo3birdtalk

2
A prima vista, funziona per me. Ma scopro che è solo per il primo strato della variabile. Ad esempio, funziona quando let a = {value: "old"} let b = [...a] b.value = "new", e a sarà {value: "old"}, b sarà {value: "new"}. Ma non funziona quando let a = [{value: "old"}] let b = [...a] b[0].value = "new", e a sarà [{value: "new"}], b sarà [{value: "new"}].
Brady Huang

sì, copia superficiale. Se hai un oggetto annidato devi fare una copia profonda. Per semplicità puoi usare le funzioni clone e deepClone di lodash
Geetanshu Gulati

3

Avevo bisogno di copiare un oggetto per valore (non riferimento) e ho trovato utile questa pagina:

Qual è il modo più efficiente per clonare in profondità un oggetto in JavaScript? . In particolare, clonare un oggetto con il seguente codice di John Resig:

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

La domanda riguarda JavaScript, non la libreria jQuery.
j08691

1
È vero, per le persone che non possono usare Jquery per qualsiasi motivo questo non li aiuterebbe. Tuttavia, poiché Jquery è una libreria javascript, penso che aiuterà ancora la maggior parte degli utenti javascript.
Anthony Haffey

Non aiuterà nessuno dei programmatori che tentano di farlo sul server. Quindi no. Non è una risposta valida.
Tomasz Gałkowski,

3

Con la sintassi ES6:

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


3
Menzione molto buona @galkowskit. Il problema più grande (meglio: cosa da notare ) con Object.assignè che non esegue una copia completa.
vol7ron

1

In sintesi, è solo un proxy eccessivamente complicato, ma forse Catch-All Proxies potrebbe farlo?

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, sono un po 'in ritardo nel rispondere :) ma penso di non averlo mai fatto per alcuni motivi: stavo cercando uno zucchero sintattico che dava a JS la capacità di passare oggetti in modi diversi; la tua risposta sembrava lunga e sono rimasto bloccato dal tuo uso di "proxy". 3 anni dopo e sono ancora appeso all'uso. Forse non ho prestato abbastanza attenzione in classe, ma come il networking, pensavo che i proxy in CS dovessero fungere da intermediari. Forse è anche qui e non sto pensando correttamente a cosa sia una mediazione.
vol7ron

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.