web-dev-qa-db-fra.com

Copier la valeur d'une variable dans une autre

J'ai une variable qui a un objet JSON comme valeur. J'attribue directement cette variable à une autre variable afin qu'elle partage la même valeur. Voilà comment cela fonctionne:

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

Cela fonctionne et les deux ont la même valeur. J'utilise un gestionnaire d'événements mousemove pour mettre à jour b dans mon application. Sur un clic de bouton, je veux rétablir b à la valeur d'origine, ce qui signifie la valeur stockée dans a.

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

Après cela, si j'utilise le même gestionnaire d'événements mousemove, il met à jour à la fois a et b; plus tôt, il ne mettait à jour que b comme prévu.

Je suis perplexe sur ce problème! Qu'est-ce qui ne va pas ici?

69
Rutwick Gangurde

Il est important de comprendre ce que fait et ne fait pas l'opérateur = en JavaScript.

L'opérateur = ne crée pas de copie des données.

L'opérateur = crée une nouvelle référence aux données identiques.

Après avoir exécuté votre code d'origine:

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

a et b sont maintenant deux noms différents pour le même objet.

Toute modification que vous apportez au contenu de cet objet sera vue de manière identique, que vous le référenciez via la variable a ou la variable b. Ils sont le même objet.

Ainsi, lorsque vous tenterez plus tard de "rétablir" b dans l'objet a d'origine avec ce code:

b = a;

Le code fait en fait rien du tout} _, car a et b sont exactement la même chose. Le code est le même que si vous aviez écrit:

b = b;

qui évidemment ne fera rien.

Pourquoi votre nouveau code fonctionne-t-il?

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

Ici, vous créez un nouvel objet avec le littéral d'objet {...}. Ce nouvel objet n'est pas le même que votre ancien objet. Donc, vous définissez maintenant b comme référence à ce nouvel objet, qui fait ce que vous voulez.

Pour manipuler n'importe quel objet arbitraire, vous pouvez utiliser une fonction de clonage d'objet telle que celle listée dans la réponse d'Armand, ou puisque vous utilisez jQuery, utilisez simplement la fonction $.extend() . Cette fonction fera une copie superficielle ou profonde d'un objet. (Ne confondez pas ceci avec la méthode $().clone() qui sert à copier des éléments DOM et non des objets.)

Pour une copie superficielle:

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

Ou une copie en profondeur:

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

Quelle est la différence entre une copie superficielle et une copie profonde? Une copie superficielle est similaire à votre code qui crée un nouvel objet avec un littéral d'objet. Il crée un nouvel objet de niveau supérieur contenant des références aux mêmes propriétés que l'objet d'origine.

Si votre objet ne contient que des types primitifs tels que des nombres et des chaînes, une copie profonde et une copie superficielle feront exactement la même chose. Mais si votre objet contient d'autres objets ou des tableaux imbriqués, une copie superficielle ne permet pas de copier objets, mais crée simplement des références. Donc, vous pourriez avoir le même problème avec les objets imbriqués que vous aviez avec votre objet de niveau supérieur. Par exemple, étant donné cet objet:

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

Si vous faites une copie superficielle de cet objet, la propriété x de votre nouvel objet est identique au même objet x de l'original:

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

Maintenant, vos objets ressembleront à ceci:

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

Vous pouvez éviter cela avec une copie en profondeur. La copie profonde revient dans chaque objet et tableau imbriqué (et dans le code d'Armand) pour faire des copies de ces objets de la même manière qu'il a copié l'objet de niveau supérieur. Donc, changer copy.x.y n'affecterait pas obj.x.y.

Réponse courte: En cas de doute, vous souhaitez probablement une copie en profondeur.

167
Michael Geary

J'ai trouvé comment utiliser JSON, mais surveillez nos références circulaires

var newInstance = JSON.parse(JSON.stringify(firstInstance));
45
kernowcode

la question est déjà résolue depuis assez longtemps, mais une solution possible pour référence future est

b = a.slice(0);

Attention, cela ne fonctionne correctement que si a est un tableau non imbriqué de nombres et de chaînes.

20
marcosh

La raison en est simple. JavaScript utilise refereces. Ainsi, lorsque vous affectez b = a, vous affectez une référence à b. Ainsi, lors de la mise à jour de a, vous mettez également à jour b.

J'ai trouvé this sur stackoverflow et j'éviterai que de telles choses ne se reproduisent à l'avenir en appelant simplement cette méthode si vous souhaitez créer une copie complète d'un objet.

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.");
}
13
Armand

newVariable = originalVariable.valueOf ();

pour les objets que vous pouvez utiliser, b = Object.assign ({}, a);

7
Kishor Patil

Je ne comprends pas pourquoi les réponses sont si complexes. En Javascript, les primitives (chaînes, nombres, etc.) sont passées par valeur et copiées. Les objets, y compris les tableaux, sont passés par référence. Dans tous les cas, l'affectation d'une nouvelle valeur ou d'une référence d'objet à 'a' ne changera pas 'b'. Mais changer le contenu de 'a' changera le contenu 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'

Collez l'une des lignes ci-dessus (une à la fois) dans le nœud ou dans une console JavaScript du navigateur. Ensuite, tapez une variable et la console affichera sa valeur.

6
Trenton D. Adams

Pour les chaînes ou les valeurs d'entrée, vous pouvez simplement utiliser ceci:

var a = $('#some_hidden_var').val(),
b = a.substr(0);
4
Tim

La plupart des réponses ici utilisent des méthodes intégrées ou des bibliothèques/frameworks. Cette méthode simple devrait bien fonctionner:

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 }
1
Lasse Brustad

Une solution pour AngularJS :

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

Je l'ai résolu moi-même pour le moment. La valeur d'origine n'a que 2 sous-propriétés. J'ai reformé un nouvel objet avec les propriétés de a, puis je l'ai affecté à b. Désormais, mon gestionnaire d'événements ne met à jour que b et mon a d'origine reste en l'état.

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

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

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

Cela fonctionne bien. Je n'ai pas changé une seule ligne dans mon code, à l'exception de ce qui précède, et cela fonctionne exactement comme je le voulais. Alors, croyez-moi, rien d'autre ne mettait à jour a.

0
Rutwick Gangurde