Copier la valeur d'une variable dans une autre

100

J'ai une variable qui a un objet JSON comme valeur. J'attribue directement cette variable à une autre variable afin qu'elles partagent 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 mousemovegestionnaire d'événements pour mettre à jour bmon application. Lors d'un clic sur un bouton, je souhaite revenir bà la valeur d'origine, c'est-à-dire à la valeur stockée dans a.

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

Après cela, si j'utilise le même mousemovegestionnaire d'événements, il met à jour les deux aet b, auparavant, il ne se mettait à jour que bcomme prévu.

Je suis perplexe sur ce problème! Quel est le problème ici?

Rutwick Gangurde
la source
Veuillez montrer votre gestionnaire mousemove. De plus, étant donné que cela a aété défini à partir de, .val()je suppose que c'est JSON (une chaîne), pas un objet - est-ce vrai? Utilisez-vous JSON.parse(a)à un moment donné pour obtenir un objet réel?
nnnnnn
Oui, c'est une chaîne mais je l'utilise $.parseJSONpour la convertir en objet. Structure: { 'key': {...}, 'key': {...}, ...}. Désolé, je ne peux pas publier de code ici, non autorisé sur mon lieu de travail!
Rutwick Gangurde
1
est adonc un objet?
Armand
1
On dirait un problème de portée ... est aune variable globale?
msturdy
2
Le code que vous avez affiché ne contient que des chaînes. Si vous parlez d'objets, plusieurs variables pourraient faire référence au même objet et ainsi cet objet pourrait être muté via l'une des variables. Par conséquent, sans voir davantage le code qui manipule les variables (d'où $.parseJSON()vient-il?), Il est difficile de dire quel est le problème. En ce qui concerne les règles de votre lieu de travail, vous n'avez pas à publier votre véritable code réel dans son intégralité, il suffit de proposer un exemple plus court et plus générique qui démontre le problème (et, idéalement, d'inclure un lien vers une démo en direct sur jsfiddle.net ) .
nnnnnn

Réponses:

198

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

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

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

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

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

aet bsont maintenant deux noms différents pour le même objet .

Toute modification que vous apportez au contenu de cet objet sera vue de la même manière que vous le référeniez via la avariable ou la bvariable. Ce sont le même objet.

Ainsi, lorsque vous essayez plus tard de "revenir" bà l' aobjet d' origine avec ce code:

b = a;

Le code ne fait rien du tout , car aet bsont 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 tout nouvel objet avec le {...}littéral objet. Ce nouvel objet n'est pas le même que votre ancien objet. Vous définissez donc maintenant bcomme référence ce nouvel objet, qui fait ce que vous voulez.

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

Pour une copie superficielle:

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

Ou une copie complète:

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 tableaux imbriqués à l'intérieur, alors une copie superficielle ne copie pas ces objets imbriqués, elle crée simplement des références à eux. Vous pourriez donc 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, alors la xpropriété de votre nouvel objet est le même xobjet que 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 complète. La copie profonde est récursée dans chaque objet et tableau imbriqués (et Date dans le code d'Armand) pour faire des copies de ces objets de la même manière qu'elle a fait une copie de l'objet de niveau supérieur. Donc changer copy.x.yn'affecterait pas obj.x.y.

Réponse courte: en cas de doute, vous voulez probablement une copie complète.

Michael Geary
la source
Merci, c'est ce que je cherchais. Un moyen de contourner ce problème? Dans ma situation actuelle, c'était facile à réparer puisque je n'avais que 2 propriétés. Mais il pourrait y avoir des objets plus gros.
Rutwick Gangurde
1
La réponse d'Armand a une fonction de clonage d'objet qui fera l'affaire. Ou puisque vous utilisez jQuery, vous pouvez utiliser la $.extend()fonction intégrée. Détails ci-dessus. :-)
Michael Geary
Grande explication Michael!
Rutwick Gangurde
7
@MichaelGeary C'est peut-être tatillon mais la règle ne s'applique-t-elle pas uniquement aux objets et non aux variables primitives? cela vaut peut-être la peine de le noter dans la réponse
Jonathan dos Santos
La solution de JS à cela est dans la réponse d'Armands comme l'a dit Michael Geary. =
AlexanderGriffin
57

J'ai trouvé que l'utilisation de JSON fonctionne mais surveillez nos références circulaires

var newInstance = JSON.parse(JSON.stringify(firstInstance));
kernowcode
la source
2
Je me rends compte que c'est un peu tard, mais y a-t-il un problème avec cette méthode? Cela semble fonctionner à merveille du premier coup.
Mankind1023
2
C'est génial dans presque toutes les situations, mais si vous travaillez avec des données qui ont le potentiel d'être ciculaires, cela plantera votre programme. du code pour illustrer: let a = {}; let b = {a:a}; a.b = b; JSON.stringify(a)donnera un TypeError
schu34
Cette solution fonctionne très bien. Cependant, combien cela coûte-t-il pendant le traitement? Il semble que cela fonctionne en double négation.
Abel Callejo le
29

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

b = a.slice(0);

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

Marcosh
la source
23

newVariable = originalVariable.valueOf();

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

Kishor Patil
la source
3
pour les objets que vous pouvez utiliser, b = Object.assign ({}, a);
Kishor Patil
15

La raison en est simple. JavaScript utilise des références, donc lorsque vous affectez, b = avous attribuez une référence à bainsi lors de la mise à jour, avous mettez également à jourb

J'ai trouvé cela sur stackoverflow et je vais aider à éviter des choses comme celle-ci à l'avenir en appelant simplement cette méthode si vous souhaitez faire 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.");
}
Armand
la source
Merci! J'ai vu ce message avant de publier une nouvelle question. Mais je ne savais pas si cela aiderait dans mon scénario. Il s'avère que c'est ce dont j'ai besoin.
Rutwick Gangurde
Ne devrait-il pas tout sauf le premier if (obj instanceof x) { ... }être autrement si?
Zac
@Zac Pas dans ce cas. Chaque if-body (d'intérêt) renvoie une valeur. (quitte la fonction avant le suivant si)
Bitterblue
8

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'attribution d'une nouvelle valeur ou 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 n'importe quelle console javascript du navigateur. Tapez ensuite n'importe quelle variable et la console affichera sa valeur.

Trenton D. Adams
la source
4

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);
Tim
la source
Pourquoi voudriez-vous sous-chaîne une primitive? Attribuez-le simplement, il copie la chaîne. Seuls les objets et les tableaux (qui sont des objets) sont passés par référence.
Trenton
2

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

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
la source
Génie! D'autres méthodes transforment tout en dictionnaire, y compris les tableaux à l'intérieur. Avec cela, je clone l'objet et le contenu reste intact, contrairement à ceci:Object.assign({},a)
Tiki
0

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 attribué b. Désormais, mon gestionnaire d'événements est mis à jour uniquement bet mon original areste tel quel.

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 très bien. Je n'ai changé aucune ligne n'importe où 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 n'a été mis à jour a.

Rutwick Gangurde
la source
0

Une solution pour AngularJS :

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