JavaScript: ¿Cómo pasar objetos por valor?

96
  • Al pasar objetos como parámetros, JavaScript los pasa por referencia y dificulta la creación de copias locales de los objetos.

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

    otendrá .fooy .bar.

  • Es posible evitar esto mediante la clonación; ejemplo simple:

    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)

    ono tendrá .fooo .bar.


Pregunta

  1. ¿Existe una mejor manera de pasar objetos por valor, además de crear una copia / clon local?
vol7ron
fuente
3
¿Cuál es su caso de uso para necesitar esto?
jondavidjohn
2
Programación divertida. Ver si los nuevos motores JS han abordado esto (técnicamente, está pasando la referencia por valor), pero principalmente por diversión.
vol7ron
1
Consulte ¿Es JavaScript un lenguaje de paso por referencia o paso por valor? - JavaScript no pasa por referencia. Al igual que Java, al pasar objetos a una función, pasa por valor, pero el valor es una referencia. Consulte también ¿Java se pasa por referencia?.
Richard JP Le Guen
1
Hasta donde yo sé, no se pueden pasar objetos por valor. Incluso si lo hubiera, de hecho estaría haciendo la clonación a la que te refieres anteriormente, así que no hay nada que ganar que pueda ver. Aparte de quizás guardar 3 líneas de código.
Thor84no
1
@ vol7ron Sí, lo han abordado implementando correctamente una característica de diseño de lenguaje.
jondavidjohn

Respuestas:

61

Realmente no.

Dependiendo de lo que realmente necesite, una posibilidad puede ser establecer ocomo prototipo de un nuevo objeto.

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

alert( o.foo ); // undefined

Entonces, cualquier propiedad que agregue obj no se agregarán o. Cualquier propiedad agregada objcon el mismo nombre de propiedad que una propiedad en osombreará la opropiedad.

Por supuesto, cualquier propiedad agregada oestará disponible en objsi no está sombreada, y todos los objetos que tengan oen la cadena de prototipos verán las mismas actualizaciones o.

También si obj tiene una propiedad que hace referencia a otro objeto, como un Array, deberá asegurarse de sombrear ese objeto antes de agregar miembros al objeto; de lo contrario, esos miembros se agregarán objy se compartirán entre todos los objetos que tener objen la cadena de prototipos.

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

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

})(o);

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

Aquí puede ver eso porque no siguió la matriz en bazelo con una bazpropiedad de objla o.bazmatriz es modificado.

Entonces, en cambio, necesitarías sombrearlo primero:

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

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

})(o);

alert( o.baz[0] );  // undefined
usuario113716
fuente
3
+1, también iba a sugerir Object.createen mi respuesta, pero no quería estirarme para explicar la superficialidad de la copia ;-)
Andy E
+1, me olvidé de esto. Lo intenté var obj = new Object(x). Para mí, creo que new Object(x)funcionaría Object.create(x)de forma predeterminada - interesante
vol7ron
1
@ vol7ron: Sí, new Object(x)simplemente escupe el mismo objeto (como probablemente hayas notado) . Sería bueno si hubiera una forma nativa de clonar. No estoy seguro de si hay algo en proceso.
user113716
43

Consulte esta respuesta https://stackoverflow.com/a/5344074/746491 .

En resumen, JSON.parse(JSON.stringify(obj))es una forma rápida de copiar sus objetos, si sus objetos se pueden serializar a json.

svimre
fuente
2
Tiendo a evitar esto ya que los objetos no siempre se pueden serializar y porque en navegadores de mediana edad como IE7 / 8, JSONno está incluido y debe definirse; bien podría ser más descriptivo con un nombre como Clone. Sin embargo, sospecho que mi opinión puede cambiar en el futuro, ya que JSON está más integrado en software no basado en navegador como bases de datos e IDE
vol7ron
1
Si no hay funciones ni objetos de fecha, la solución JSON.parse parece ser la mejor. stackoverflow.com/questions/122102/…
Herr_Hansen
¿¡¿De Verdad?!? ¿Serializar a JSON solo para crear una copia de objeto? Otra razón más para odiar este lenguaje absurdo.
RyanNerd
1
¿Serializar JSON es realmente una forma rápida de copiarlo? Según la documentación de NodeJS, la manipulación JSON es la tarea más intensiva de la CPU. nodejs.org/en/docs/guides/dont-block-the-event-loop/…
harshad
38

Aquí está la función de clonación que realizará una copia profunda del objeto:

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

Ahora puedes usarlo así:

(function(x){
    var obj = clone(x);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o)
Paul Varghese
fuente
3
Esta fue una respuesta profunda.
Nathan
¡Hermoso! Mejor respuesta en mi opinión.
ganders
23

Utilizar Object.assign()

Ejemplo:

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

Un ejemplo más limpio que hace lo mismo:

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
fuente
4
Mejor:var b = Object.assign({}, a);
Bergi
Convenido. Actualizando la respuesta en consecuencia. Además, no todos los navegadores admiten el método assign (). Dependiendo de sus requisitos de compatibilidad, es posible que sea necesario utilizar un polyfill como el de MDN.
Brandon
3
@Brandon, en el mismo enlace que proporcionó, da una advertencia para la clonación profunda: "Para la clonación profunda, necesitamos usar otras alternativas porque Object.assign () copia los valores de propiedad. Si el valor de origen es una referencia a un objeto, sólo copia ese valor de referencia ".
pwilcox
2
Esto funciona, pero tenga en cuenta que no realiza una copia profunda, los objetos contenidos en su interior ase copiarán como referencia.
Stoinov
15

Está un poco confundido acerca de cómo funcionan los objetos en JavaScript. La referencia del objeto es el valor de la variable. No hay ningún valor sin serializar. Cuando crea un objeto, su estructura se almacena en la memoria y la variable a la que se le asignó contiene una referencia a esa estructura.

Incluso si lo que está preguntando se proporcionara en algún tipo de construcción de lenguaje nativo fácil, técnicamente sería una clonación.

JavaScript es realmente solo pasar por valor ... es solo que el valor transmitido podría ser una referencia a algo.

Andy E
fuente
1
¡Hola AndyE! No estoy confundido por eso, tenía la esperanza de que JS haría la clonación de forma predeterminada. Hay grandes ineficiencias en la auto clonación, por lo que si el motor lo hiciera, estoy seguro de que sería mejor. Sin embargo, dudo que la necesidad / demanda supere el beneficio.
vol7ron
12
En JS, cuando la gente dice por referencia en lugar de por valor, quiere decir que el valor es como un puntero en lugar de un duplicado. Esa es una jerga bastante bien establecida.
Erik Reppen
4
@Erik: en mi humilde opinión, creo que es mejor definirlo con más claridad para no confundir a los recién llegados al idioma. El hecho de que algo esté bien establecido no significa que sea apropiado o incluso técnicamente correcto (porque no lo es). La gente dice "por referencia" porque es más fácil para ellos decir eso que explicar cómo funciona realmente.
Andy E
6
El valor es una referencia a una ubicación en la memoria. En última instancia, todavía está actuando sobre el mismo elemento en la memoria en lugar de trabajar con una copia. ¿Cómo se supone que hacer esa distinción ambigua ayude a los recién llegados? Así es como se explica en muchos libros de tecnología, incluida la Guía autorizada de O'Reilly, que ahora se encuentra en su quinta edición (OT: y muy débilmente actualizada).
Erik Reppen
1
@AndyE Leí un poco más para ver por qué tanto alboroto. En mi opinión, esto es algo así como la cuestión de si JS tiene POO "verdadera" o si podemos llamar a algo un "método" en comparación con otros lenguajes. En realidad, no estoy seguro de que sea factible tratar a las vars como si escribieran directamente en el mismo objeto en la memoria en lugar de sobrescribir su puntero copiado en un lenguaje basado en el cierre con una ramificación del comportamiento del operador de asignación que destruye el rendimiento. La única diferencia de comportamiento real en lo que queremos decir en JS frente a otros lenguajes es lo que sucede cuando utiliza el operador de asignación.
Erik Reppen
15

Utilizar este

x = Object.create(x1);

xy x1serán dos objetos diferentes, el cambio xno cambiaráx1

usuario5242529
fuente
Termino usando Object.assign (). Para un caso de prueba simple, .create () funciona, pero para los más complejos (que aún no puedo encontrar el patrón), todavía se refiere a la misma dirección.
Coisox
No solo cambiará si los valores del objeto no se almacenan por referencia, es decir, son tipos de datos preceptivos como número o booleano. Por ejemplo, considere el código debajo de a = {x: [12], y: []}; b = Object.create (a); bxpush (12); console.log (hacha);
Jjagwe Dennis
8

Javascript siempre pasa por valor. En este caso, pasa una copia de la referencia oa la función anónima. El código usa una copia de la referencia pero está mutando el objeto único. No hay forma de hacer que JavaScript pase por nada que no sea el valor.

En este caso lo que quieres es pasar una copia del objeto subyacente. La clonación del objeto es el único recurso. Sin embargo, su método de clonación necesita un poco de actualización

function ShallowCopy(o) {
  var copy = Object.create(o);
  for (prop in o) {
    if (o.hasOwnProperty(prop)) {
      copy[prop] = o[prop];
    }
  }
  return copy;
}
JaredPar
fuente
1
+1 Para el "siempre pasa por valor", aunque prefiero usar la llamada por intercambio de objetos (en lugar de "Llamada por valor [de la referencia]") y promover "el valor" a "el objeto en sí" (con la nota que el objeto no se copia / clona / duplica) ya que las referencias son solo un detalle de implementación y no están expuestas a JavaScript (¡ni se mencionan directamente en la especificación!).
Tuve un problema para agregar propiedades a un objeto que estoy recibiendo de una conexión remota (pensé que ese podría ser el problema de que no puedo agregar ninguna propiedad a ese objeto), así que probé esta función de copia superficial y obtuve este error: prototipo de objeto solo puede ser un objeto o nulo en var copy = Object.create (o); alguna idea de cómo resolver mi problema
Harshit Laddha
¿No es esto una copia profunda?
River Tam
@RiverTam No, no es una copia profunda, porque no clona los objetos dentro de los objetos. Sin embargo, puede convertirlo fácilmente en una copia profunda simplemente haciendo que se llame a sí mismo de forma recursiva en cada uno de los subobjetos.
ABPerson
7

Como consideración para los usuarios de jQuery, también hay una manera de hacer esto de una manera simple usando el marco. Solo otra forma en que jQuery nos hace la vida un poco más fácil.

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

referencias:

Brett Weber
fuente
Ah, descubrió esta vieja pregunta :) Creo que la pregunta original era más una exploración del lenguaje JS. Parece que incluso mi terminología era débil en ese entonces, ya que mi ejemplo era una copia profunda en lugar de un clon . Pero parece que los objetos / hash sólo pueden ser pasados por referencia, que es común en los lenguajes de scripting
vol7ron
1
:) Lo encontré buscando la funcionalidad exacta. Tenía un objeto de datos que necesitaba copiar para editarlo antes de enviarlo mientras conservaba el objeto original. Prefiero la solución JS nativa y la usaré en mi propia biblioteca base, pero la solución jQuery es mi solución temporal. La respuesta aceptada es fantástica. : D
Brett Weber
6

En realidad, Javascript siempre se pasa por valor . Pero debido a que las referencias a objetos son valores , los objetos se comportarán como si fueran pasados ​​por referencia .

Así pues, para caminar por esta, stringify el objeto y analizar de nuevo, tanto el uso de JSON. Vea un ejemplo de código a continuación:

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
fuente
2
Sí, esta es una opción para convertir / volver a convertir de forma sencilla. Generalmente, trabajar con cadenas es muy ineficaz; sin embargo, no he evaluado el rendimiento del análisis JSON en algún tiempo. Tenga en cuenta que si bien esto funciona para objetos simples, los objetos que contienen funciones como valores perderán contenido.
vol7ron
4

use obj2 = {... obj1} Ahora ambos objetos tienen el mismo valor pero una referencia diferente

Geetanshu Gulati
fuente
2
Nuevo en javascript aquí. ¿El operador de propagación crea un nuevo valor en lugar de copiar por referencia? Entonces, ¿por qué se votó en contra?
jo3birdtalk
2
A primera vista, me funciona. Pero descubro que es solo para la primera capa de la variable. Por ejemplo, determina cuándo let a = {value: "old"} let b = [...a] b.value = "new", y será a {value: "old"}, será b {value: "new"}. Pero no funciona cuándo let a = [{value: "old"}] let b = [...a] b[0].value = "new", y será a [{value: "new"}], b será [{value: "new"}].
Brady Huang
sí, hace copia superficial. Si tiene un objeto anidado, necesita hacer una copia profunda. Para simplificar, puede usar las funciones clon y deepClone de lodash
Geetanshu Gulati
3

Necesitaba copiar un objeto por valor (no referencia) y encontré útil esta página:

¿Cuál es la forma más eficiente de clonar en profundidad un objeto en JavaScript? . En particular, clonar un objeto con el siguiente código de John Resig:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
Anthony Haffey
fuente
La pregunta es sobre JavaScript, no sobre la biblioteca jQuery.
j08691
1
Es cierto que para las personas que no pueden usar Jquery por cualquier motivo, esto no les ayudaría. Sin embargo, como Jquery es una biblioteca de JavaScript, creo que aún ayudará a la mayoría de los usuarios de JavaScript.
Anthony Haffey
No ayudará a ninguno de los programadores que intenten hacerlo en el servidor. Entonces, no. No es una respuesta válida.
Tomasz Gałkowski
3

Con la sintaxis de ES6:

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

Tomasz Gałkowski
fuente
3
Muy buena mención @galkowskit. El mayor problema (mejor: algo a tener en cuenta ) con el Object.assignes que no realiza una copia profunda.
vol7ron
1

Cuando se reduce a eso, es solo un proxy elegante demasiado complicado, pero ¿tal vez Catch-All Proxies podría hacerlo?

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
fuente
Richard, llego un poco tarde a responder :) pero creo que nunca lo hice por algunas razones: estaba buscando en azúcar sintáctico que le diera a JS la capacidad de pasar objetos de diferentes maneras; su respuesta parecía larga, y estaba colgado por su uso de "proxy". 3 años después y todavía estoy colgado del uso. Quizás no presté suficiente atención en clase, pero al igual que la creación de redes, pensé que se supone que los proxies en CS actúan como intermediarios. Quizás también esté aquí y no estoy pensando correctamente en qué es un intermediario.
vol7ron
0

Si está usando lodash o npm, use la función de combinación de lodash para copiar en profundidad todas las propiedades del objeto a un nuevo objeto vacío como este:

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

https://lodash.com/docs#merge

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

Con Antonakos
fuente
Una copia profunda también debe incluir eventos, no estoy muy familiarizado con lodash, ¿refleja los eventos en un nuevo objeto?
vol7ron