Copie o valor de uma variável para outra

100

Eu tenho uma variável que tem um objeto JSON como valor. Eu atribuo essa variável diretamente a alguma outra variável para que elas compartilhem o mesmo valor. É assim que funciona:

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

Isso funciona e ambos têm o mesmo valor. Eu uso um mousemovemanipulador de eventos para atualizar bmeu aplicativo. Com um clique de botão, desejo reverter bpara o valor original, ou seja, o valor armazenado em a.

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

Depois disso, se eu usar o mesmo mousemovemanipulador de eventos, ele atualiza ambos ae b, quando antes, ele estava atualizando apenas bconforme o esperado.

Estou perplexo com esse problema! O que há de errado aqui?

Rutwick Gangurde
fonte
Mostre seu manipulador de mouse. Além disso, considerando que afoi definido a partir de .val(), presumo que seja JSON (uma string), não um objeto - certo? Você usa JSON.parse(a)em algum ponto para obter um objeto real?
nnnnnn
Sim, é uma string, mas eu costumo $.parseJSONconvertê-la em um objeto. Estrutura: { 'key': {...}, 'key': {...}, ...}. Desculpe, não posso postar nenhum código aqui, não é permitido em meu local de trabalho!
Rutwick Gangurde
1
então é aum objeto?
Armand de
1
Parece um problema de escopo .. é auma variável global?
mês de
2
O código que você mostrou possui apenas strings. Se você estiver falando de objetos, várias variáveis ​​podem referir-se ao mesmo objeto e, portanto, esse objeto pode sofrer mutação por meio de qualquer uma das variáveis. Portanto, sem ver mais do código que manipula as variáveis ​​(onde $.parseJSON()entra isso?), É difícil dizer qual é o problema. Com relação às regras do seu local de trabalho, você não precisa postar seu código real completo, apenas crie um exemplo mais curto e genérico que demonstre o problema (e, idealmente, inclua um link para uma demonstração ao vivo em jsfiddle.net ) .
nnnnnn

Respostas:

198

É importante entender o que o =operador em JavaScript faz e o que não faz.

O =operador não faz uma cópia dos dados.

O =operador cria uma nova referência para os mesmos dados.

Depois de executar seu código original:

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

ae bagora são dois nomes diferentes para o mesmo objeto .

Qualquer alteração que você fizer no conteúdo deste objeto será vista de forma idêntica, quer você faça referência a ela por meio da avariável ou dob variável. Eles são o mesmo objeto.

Então, quando você tentar "reverter" mais tarde bpara o aobjeto original com este código:

b = a;

O código realmente não faz nada , porque ae bsão exatamente a mesma coisa. O código é o mesmo que se você tivesse escrito:

b = b;

que obviamente não fará nada.

Por que seu novo código funciona?

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

Aqui você está criando um novo objeto com o {...}literal de objeto. Este novo objeto não é igual ao antigo. Portanto, agora você está definindo bcomo referência este novo objeto, que faz o que você deseja.

Para lidar com qualquer objeto arbitrário, você pode usar uma função de clonagem de objeto como a listada na resposta de Armand ou, já que está usando jQuery, apenas use a $.extend()função . Esta função fará uma cópia superficial ou profunda de um objeto. (Não confunda isso com o $().clone()método que é para copiar elementos DOM, não objetos.)

Para uma cópia superficial:

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

Ou uma cópia profunda:

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

Qual é a diferença entre uma cópia superficial e uma cópia profunda? Uma cópia superficial é semelhante ao seu código que cria um novo objeto com um literal de objeto. Ele cria um novo objeto de nível superior contendo referências às mesmas propriedades do objeto original.

Se o seu objeto contém apenas tipos primitivos, como números e strings, uma cópia profunda e uma cópia superficial farão exatamente a mesma coisa. Mas se o seu objeto contém outros objetos ou matrizes aninhados dentro dele, então uma cópia superficial não copia esses objetos aninhados, apenas cria referências a eles. Portanto, você pode ter o mesmo problema com objetos aninhados que teve com seu objeto de nível superior. Por exemplo, dado este objeto:

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

Se você fizer uma cópia superficial desse objeto, a xpropriedade do seu novo objeto será o mesmo xobjeto do original:

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

Agora seus objetos ficarão assim:

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

Você pode evitar isso com uma cópia profunda. A cópia profunda recorre em cada objeto e matriz aninhados (e Data no código de Armand) para fazer cópias desses objetos da mesma forma que fez uma cópia do objeto de nível superior. Portanto, a mudança copy.x.ynão afetariaobj.x.y .

Resposta curta: em caso de dúvida, você provavelmente deseja uma cópia profunda.

Michael Geary
fonte
Obrigado, isso é o que eu estava procurando. Alguma maneira de contornar isso? Na minha situação atual, foi fácil de consertar, pois eu tinha apenas 2 propriedades. Mas pode haver objetos maiores.
Rutwick Gangurde
1
A resposta de Armand tem uma função de clonagem de objeto que fará o truque. Ou já que está usando jQuery, você pode usar a $.extend()função integrada. Detalhes acima. :-)
Michael Geary
Ótima explicação Michael!
Rutwick Gangurde
7
@MichaelGeary Pode ser picuinhas, mas a regra não se aplica apenas a objetos e não a variáveis ​​primitivas? pode valer a pena notar na resposta
Jonathan dos Santos
A solução JS para isso está na resposta de Armands, como Michael Geary disse. =
AlexanderGriffin
57

Descobri que usar JSON funciona, mas observe nosso para referências circulares

var newInstance = JSON.parse(JSON.stringify(firstInstance));
kernowcode
fonte
2
Sei que é um pouco tarde, mas há algum problema com esse método? Parece funcionar muito bem na primeira tentativa.
Mankind1023
2
Isso é ótimo em quase todas as situações; no entanto, se você estiver trabalhando com dados que têm o potencial de serem específicos, seu programa travará. algum código para ilustrar: let a = {}; let b = {a:a}; a.b = b; JSON.stringify(a)dará um
TypeError
Esta solução funciona muito bem. No entanto, quão caro é isso durante o processamento? Parece que isso funciona por dupla negação.
Abel Callejo
29

a questão já está resolvida há muito tempo, mas para referência futura uma possível solução é

b = a.slice(0);

Tenha cuidado, isso funciona corretamente apenas se a for uma matriz não aninhada de números e strings

marcosh
fonte
23

newVariable = originalVariable.valueOf();

para objetos que você pode usar, b = Object.assign({},a);

Kishor Patil
fonte
3
para objetos que você pode usar, b = Object.assign ({}, a);
Kishor Patil
15

A razão para isso é simples. JavaScript usa referências, então, quando você atribui b = auma referência b, avocê também está atualizandob

Eu encontrei isso no stackoverflow e ajudarei a evitar coisas como essa no futuro apenas chamando esse método se você quiser fazer uma cópia profunda de um 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
fonte
Obrigado! Eu vi essa postagem antes de postar uma nova pergunta. Mas eu não tinha certeza se isso ajudaria no meu cenário. Acontece que é disso que preciso.
Rutwick Gangurde,
Não deveriam ser todos, exceto o primeiro, if (obj instanceof x) { ... }senão?
Zac
@Zac Não neste caso. Cada if-body (de interesse) retorna um valor. (sai da função antes do próximo if)
Bitterblue
8

Não entendo por que as respostas são tão complexas. Em Javascript, primitivos (strings, números, etc) são passados ​​por valor e copiados. Objetos, incluindo matrizes, são passados ​​por referência. Em qualquer caso, a atribuição de um novo valor ou referência de objeto a 'a' não mudará 'b'. Mas alterar o conteúdo de 'a' mudará o conteúdo 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'

Cole qualquer uma das linhas acima (uma de cada vez) no nó ou em qualquer console javascript do navegador. Em seguida, digite qualquer variável e o console mostrará seu valor.

Trenton D. Adams
fonte
4

Para strings ou valores de entrada, você pode simplesmente usar isto:

var a = $('#some_hidden_var').val(),
b = a.substr(0);
Tim
fonte
Por que você colocaria substring em um primitivo? Basta atribuí-lo, ele copia a string. Apenas objetos e matrizes (que são objetos) são passados ​​por referência.
Trenton D. Adams
2

A maioria das respostas aqui está usando métodos integrados ou usando bibliotecas / estruturas. Este método simples deve funcionar bem:

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
fonte
Gênio! Outros métodos transformam tudo em um dicionário, incluindo matrizes dentro deles. Com isso eu clono o objeto e o conteúdo permanece intacto, ao contrário deste:Object.assign({},a)
Tiki
0

Eu mesmo resolvi por enquanto. O valor original tem apenas 2 subpropriedades. Reformulei um novo objeto com as propriedades de ae atribuí a ele b. Agora, meu manipulador de eventos é atualizado apenas be o 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 };
});

Isso funciona bem. Eu não mudei uma única linha em qualquer lugar do meu código, exceto a acima, e funciona exatamente como eu queria. Então, acredite em mim, nada mais estava atualizando a.

Rutwick Gangurde
fonte
0

Uma solução para AngularJS :

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