Copiar el valor de una variable en otra

100

Tengo una variable que tiene un objeto JSON como valor. Asigno directamente esta variable a alguna otra variable para que compartan el mismo valor. Así es como funciona:

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

Esto funciona y ambos tienen el mismo valor. Utilizo un mousemovecontrolador de eventos para actualizar bmi aplicación. Al hacer clic en un botón, quiero volver bal valor original, es decir, el valor almacenado en a.

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

Después de esto, si uso el mismo mousemovecontrolador de eventos, actualiza ambos ay b, cuando antes, se actualizaba solo bcomo se esperaba.

¡Estoy perplejo por este problema! ¿Que esta mal aquí?

Rutwick Gangurde
fuente
Muestra tu controlador mousemove. Además, dado que ase configuró desde .val(), supongo que es JSON (una cadena), no un objeto, ¿es así? ¿Usas JSON.parse(a)en algún momento para obtener un objeto real?
nnnnnn
Sí, es una cadena, pero la uso $.parseJSONpara convertirla en un objeto. Estructura: { 'key': {...}, 'key': {...}, ...}. Lo siento, no puedo publicar ningún código aquí, ¡no está permitido en mi lugar de trabajo!
Rutwick Gangurde
1
también lo es aun objeto?
Armand
1
Parece un problema de alcance ... ¿es auna variable global?
2013
2
El código que ha mostrado solo tiene cadenas. Si está hablando de objetos, entonces múltiples variables podrían referirse al mismo objeto y, por lo tanto, ese objeto podría mutar a través de cualquiera de las variables. Por lo tanto, sin ver más código que manipula las variables (¿dónde $.parseJSON()entra?) Es difícil decir cuál es el problema. Con respecto a las reglas de su lugar de trabajo, no tiene que publicar su código real real en su totalidad, solo presente un ejemplo más corto y más genérico que demuestre el problema (e, idealmente, incluya un enlace a una demostración en vivo en jsfiddle.net ) .
nnnnnn

Respuestas:

198

Es importante comprender qué =hace y qué no hace el operador en JavaScript.

El =operador no realiza una copia de los datos.

El =operador crea una nueva referencia a los mismos datos.

Después de ejecutar su código original:

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

ay bahora son dos nombres diferentes para el mismo objeto .

Cualquier cambio que realice en el contenido de este objeto se verá de manera idéntica ya sea que haga referencia a él a través de la avariable o la bvariable. Son el mismo objeto.

Entonces, cuando más tarde intente "volver" bal aobjeto original con este código:

b = a;

El código en realidad no hace nada en absoluto , porque ay bson exactamente lo mismo. El código es el mismo que si hubiera escrito:

b = b;

que obviamente no hará nada.

¿Por qué funciona tu nuevo código?

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

Aquí está creando un objeto nuevo con el {...}objeto literal. Este nuevo objeto no es el mismo que su antiguo objeto. Así que ahora estás configurandob como referencia a este nuevo objeto, que hace lo que quiere.

Para manejar cualquier objeto arbitrario, puede usar una función de clonación de objetos como la que se enumera en la respuesta de Armand, o como está usando jQuery, simplemente use la $.extend()función . Esta función hará una copia superficial o una copia profunda de un objeto. (No confunda esto con el $().clone()método que es para copiar elementos DOM, no objetos).

Para una copia superficial:

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

O una copia profunda:

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

¿Cuál es la diferencia entre una copia superficial y una profunda? Una copia superficial es similar a su código que crea un nuevo objeto con un objeto literal. Crea un nuevo objeto de nivel superior que contiene referencias a las mismas propiedades que el objeto original.

Si su objeto contiene solo tipos primitivos como números y cadenas, una copia profunda y una copia superficial harán exactamente lo mismo. Pero si su objeto contiene otros objetos o matrices anidadas dentro de él, entonces una copia superficial no copia esos objetos anidados, simplemente crea referencias a ellos. Entonces, podría tener el mismo problema con los objetos anidados que tenía con su objeto de nivel superior. Por ejemplo, dado este objeto:

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

Si hace una copia superficial de ese objeto, entonces la xpropiedad de su nuevo objeto es el mismo xobjeto del original:

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

Ahora sus objetos se verán así:

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

Puede evitar esto con una copia profunda. La copia profunda se repite en cada objeto y matriz anidados (y Fecha en el código de Armand) para hacer copias de esos objetos de la misma manera que hizo una copia del objeto de nivel superior. Así que cambiar copy.x.yno afectaría obj.x.y.

Respuesta corta: en caso de duda, probablemente desee una copia detallada.

Michael Geary
fuente
Gracias, esto es lo que estaba buscando. ¿Alguna forma de evitarlo? En mi situación actual, fue fácil de arreglar ya que solo tenía 2 propiedades. Pero podría haber objetos más grandes.
Rutwick Gangurde
1
La respuesta de Armand tiene una función de clonación de objetos que hará el truco. O como está usando jQuery, puede usar la $.extend()función incorporada. Detalles arriba. :-)
Michael Geary
¡Gran explicación Michael!
Rutwick Gangurde
7
@MichaelGeary Puede ser quisquilloso, pero ¿no se aplica la regla solo a objetos y no a variables primitivas? Vale la pena señalarlo en la respuesta
Jonathan dos Santos
La solución de JS para esto está en la respuesta de Armands como dijo Michael Geary. =
AlexanderGriffin
57

Encontré que usar JSON funciona, pero observe nuestras referencias circulares

var newInstance = JSON.parse(JSON.stringify(firstInstance));
kernowcode
fuente
2
Me doy cuenta de que es un poco tarde, pero ¿hay algún problema con este método? Parece funcionar increíblemente en el primer intento.
Mankind1023
2
Esto es excelente en casi todas las situaciones, sin embargo, si está trabajando con datos que tienen el potencial de ser circulares, bloqueará su programa. algo de código para ilustrar: let a = {}; let b = {a:a}; a.b = b; JSON.stringify(a)dará un TypeError
schu34
Esta solución funciona muy bien. Sin embargo, ¿qué tan caro es esto durante el procesamiento? Parece que esto funciona mediante una doble negación.
Abel Callejo
29

la pregunta ya está resuelta desde hace bastante tiempo, pero para referencia futura una posible solución es

b = a.slice(0);

Tenga cuidado, esto funciona correctamente solo si a es una matriz no anidada de números y cadenas

marcosh
fuente
23

newVariable = originalVariable.valueOf();

para los objetos que puedes usar, b = Object.assign({},a);

Kishor Patil
fuente
3
para los objetos que puede usar, b = Object.assign ({}, a);
Kishor Patil
15

La razón de esto es simple. JavaScript usa referencias, por lo que cuando asigna b = a, está asignando una referencia a, por lo btanto, al actualizar a, también está actualizandob

Encontré esto en stackoverflow y ayudaré a prevenir cosas como esta en el futuro simplemente llamando a este método si desea hacer una copia profunda de un objeto.

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
fuente
¡Gracias! Vi esa publicación antes de publicar una nueva pregunta. Pero no estaba seguro de si eso ayudaría en mi escenario. Resulta que eso es lo que necesito.
Rutwick Gangurde
¿No debería ser todo menos el primero if (obj instanceof x) { ... }si?
Zac
@Zac No en este caso. Cada if-body (de interés) devuelve un valor. (sale de la función antes del próximo si)
Bitterblue
8

No entiendo por qué las respuestas son tan complejas. En Javascript, las primitivas (cadenas, números, etc.) se pasan por valor y se copian. Los objetos, incluidas las matrices, se pasan por referencia. En cualquier caso, la asignación de un nuevo valor o referencia de objeto a 'a' no cambiará 'b'. Pero cambiar el contenido de 'a' cambiará el contenido de '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'

Pegue cualquiera de las líneas anteriores (una a la vez) en el nodo o en cualquier consola javascript del navegador. Luego escriba cualquier variable y la consola mostrará su valor.

Trenton D. Adams
fuente
4

Para cadenas o valores de entrada, simplemente puede usar esto:

var a = $('#some_hidden_var').val(),
b = a.substr(0);
Tim
fuente
¿Por qué subcadena un primitivo? Simplemente asígnelo, copia la cadena. Solo los objetos y las matrices (que son objetos) se pasan por referencia.
Trenton D. Adams
2

La mayoría de las respuestas aquí usan métodos integrados o bibliotecas / marcos. Este método simple debería funcionar bien:

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
fuente
¡Genio! Otros métodos convierten todo en diccionario, incluidas las matrices dentro de ellos. Con esto clono el objeto y el contenido permanece intacto, a diferencia de esto:Object.assign({},a)
Tiki
0

Lo resolví yo mismo por el momento. El valor original tiene solo 2 subpropiedades. Reformé un nuevo objeto con las propiedades de ay luego se lo asigné b. Ahora mi controlador de eventos solo se actualiza b, y mi original apermanece como está.

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

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

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

Esto funciona bien. No he cambiado una sola línea en ningún lugar de mi código, excepto lo anterior, y funciona exactamente como quería. Entonces, créame, nada más se estaba actualizando a.

Rutwick Gangurde
fuente
0

Una solución para AngularJS :

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