118

I have a variable which has a JSON object as its value. I directly assign this variable to some other variable so that they share the same value. This is how it works:

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

This works and both have the same value. I use a mousemove event handler to update b through out my app. On a button click, I want to revert b to the original value, meaning the value stored in a.

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

After this if I use the same mousemove event handler, it updates both a and b, when earlier it was updating only b as expected.

I'm stumped over this issue! What is wrong here?

Gemtastic
  • 6,253
  • 6
  • 34
  • 53
Rutwick Gangurde
  • 4,772
  • 11
  • 53
  • 87
  • Please show your mousemove handler. Also, given that `a` was set from `.val()` I assume it is JSON (a string), not an object - is that right? Do you use `JSON.parse(a)` at some point to get an actual object? – nnnnnn Sep 16 '13 at 13:23
  • Yes it is a string but I use `$.parseJSON` to convert it to an object. Structure: `{ 'key': {...}, 'key': {...}, ...}`. Sorry cannot post any code here, not allowed in my workplace! – Rutwick Gangurde Sep 16 '13 at 13:27
  • 1
    so is `a` is an object? – Armand Sep 16 '13 at 13:32
  • 1
    Looks like a scoping issue.. is `a` a global variable? – msturdy Sep 16 '13 at 13:32
  • 2
    The code you have shown only has strings. If you're talking objects then multiple variables could refer to the same object and so that object could be mutated via any of the variables. Therefore without seeing more of the code that manipulates the variables (where does `$.parseJSON()` come into it?) it is hard to say what the problem is. Regarding your workplace rules, you don't have to post your actual real code in full, just come up with a shorter and more generic example that demonstrates the problem (and, ideally, include a link to a live demo at http://jsfiddle.net). – nnnnnn Sep 16 '13 at 13:34
  • I've make a simple jsfiddle with strings... http://jsfiddle.net/msturdy/PbujS/ it seems to work ok! – msturdy Sep 16 '13 at 14:15
  • When I need to post code here on SO, I rename anything that can identify the company, project, client, etc. If there's proprietary logic involved, I either skip it on SO or refactor it into another method. – Codes with Hammer Sep 16 '13 at 14:41
  • @CodeswithHammer Agreed, and I already follow that rule. But can't do when I need to meet a deadline. – Rutwick Gangurde Sep 17 '13 at 06:49
  • @nnnnnn It is an object, and only one of my handlers updates it. I've rechecked the code twice, nothing else updates it. `parseJSON` is used only once when I read the value from the hidden field and store it in that variable. – Rutwick Gangurde Sep 17 '13 at 06:53
  • @msturdy Not actually. Since I'm using the modular pattern, its a property of my namespace. – Rutwick Gangurde Sep 17 '13 at 07:02
  • I would think there should be an easy way to do this! Oh well :) – www139 Dec 24 '15 at 04:30

10 Answers10

222

It's important to understand what the = operator in JavaScript does and does not do.

The = operator does not make a copy of the data.

The = operator creates a new reference to the same data.

After you run your original code:

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

a and b are now two different names for the same object.

Any change you make to the contents of this object will be seen identically whether you reference it through the a variable or the b variable. They are the same object.

So, when you later try to "revert" b to the original a object with this code:

b = a;

The code actually does nothing at all, because a and b are the exact same thing. The code is the same as if you'd written:

b = b;

which obviously won't do anything.

Why does your new code work?

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

Here you are creating a brand new object with the {...} object literal. This new object is not the same as your old object. So you are now setting b as a reference to this new object, which does what you want.

To handle any arbitrary object, you can use an object cloning function such as the one listed in Armand's answer, or since you're using jQuery just use the $.extend() function. This function will make either a shallow copy or a deep copy of an object. (Don't confuse this with the $().clone() method which is for copying DOM elements, not objects.)

For a shallow copy:

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

Or a deep copy:

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

What's the difference between a shallow copy and a deep copy? A shallow copy is similar to your code that creates a new object with an object literal. It creates a new top-level object containing references to the same properties as the original object.

If your object contains only primitive types like numbers and strings, a deep copy and shallow copy will do exactly the same thing. But if your object contains other objects or arrays nested inside it, then a shallow copy doesn't copy those nested objects, it merely creates references to them. So you could have the same problem with nested objects that you had with your top-level object. For example, given this object:

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

If you do a shallow copy of that object, then the x property of your new object is the same x object from the original:

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

Now your objects will look like this:

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

You can avoid this with a deep copy. The deep copy recurses into every nested object and array (and Date in Armand's code) to make copies of those objects in the same way it made a copy of the top-level object. So changing copy.x.y wouldn't affect obj.x.y.

Short answer: If in doubt, you probably want a deep copy.

Michael Geary
  • 28,450
  • 9
  • 65
  • 75
  • Thanks, this is what I was looking for. Any way to work around it? In my current situation, it was easy to fix since I had only 2 properties. But there could be larger objects. – Rutwick Gangurde Sep 17 '13 at 08:51
  • 1
    Armand's answer has an object clone function that will do the trick. Or since you're using jQuery, you can use the built-in `$.extend()` function. Details above. :-) – Michael Geary Sep 17 '13 at 15:46
  • 8
    @MichaelGeary It might be nitpicking but don't the rule apply only to objects and not primitive variables? it might worth noting in the answer – Jonathan DS Jan 20 '14 at 17:15
  • JS solution to this is in Armands answer like Michael Geary said.= – AlexanderGriffin Sep 18 '16 at 16:38
66

I found using JSON works but watch our for circular references

var newInstance = JSON.parse(JSON.stringify(firstInstance));
Jay Byford-Rew
  • 5,736
  • 1
  • 35
  • 36
  • 2
    I realise this is a bit late, but is there a problem with this method? It seems to work amazingly on first try. – Mankind1023 Aug 22 '16 at 17:57
  • 3
    This is great in almost every situation, however if you're working with data that has the potential to be cicular, it will crash your program. some code to illustrate: `let a = {}; let b = {a:a}; a.b = b; JSON.stringify(a)` will give a TypeError – schu34 Feb 05 '18 at 22:21
  • This solution works great. However, how expensive is this during processing? It seems this works by double negating. – Abel Callejo Jun 23 '20 at 22:52
59

newVariable = originalVariable.valueOf();

for objects you can use, b = Object.assign({},a);

C.OG
  • 6,236
  • 3
  • 20
  • 38
Kishor Patil
  • 900
  • 7
  • 9
36

the question is already solved since quite a long time, but for future reference a possible solution is

b = a.slice(0);

Be careful, this works correctly only if a is a non-nested array of numbers and strings

marcosh
  • 8,780
  • 5
  • 44
  • 74
18

The reason for this is simple. JavaScript uses refereces, so when you assign b = a you are assigning a reference to b thus when updating a you are also updating b

I found this on stackoverflow and will help prevent things like this in the future by just calling this method if you want to do a deep copy of an object.

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.");
}
Community
  • 1
  • 1
Armand
  • 9,847
  • 9
  • 42
  • 75
12

I do not understand why the answers are so complex. In Javascript, primitives (strings, numbers, etc) are passed by value, and copied. Objects, including arrays, are passed by reference. In any case, assignment of a new value or object reference to 'a' will not change 'b'. But changing the contents of 'a' will change the contents of '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'

Paste any of the above lines (one at a time) into node or any browser javascript console. Then type any variable and the console will show it's value.

Trenton D. Adams
  • 1,805
  • 2
  • 16
  • 20
4

For strings or input values you could simply use this:

var a = $('#some_hidden_var').val(),
b = a.substr(0);
Tim
  • 2,805
  • 25
  • 21
  • Why would you substring a primitive? Just assign it, it copies the string. Only objects, and arrays (which are objects) are passed by reference. – Trenton D. Adams Dec 25 '16 at 07:02
4

Most of the answers here are using built-in methods or using libraries/frameworks. This simple method should work fine:

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
  • 119
  • 1
  • 6
  • Genius! Other methods turn everything to dictionary, including arrays within them. With this I clone the object and the contents remain intact, unlike with this: `Object.assign({},a)` – Tiki Jan 19 '20 at 19:08
0

I solved it myself for the time being. The original value has only 2 sub-properties. I reformed a new object with the properties from a and then assigned it to b. Now my event handler updates only b, and my original a stays as it is.

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

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

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

This works fine. I have not changed a single line anywhere in my code except for the above, and it works just how I wanted it to. So, trust me, nothing else was updating a.

Rutwick Gangurde
  • 4,772
  • 11
  • 53
  • 87
0

A solution for AngularJS:

$scope.targetObject = angular.copy($scope.sourceObject)
tonysepia
  • 3,340
  • 3
  • 27
  • 45