SimplGy
SimplGy

Reputation: 20437

Javascript copy by value using $.extend, but only an object property

I am trying to copy a property by value so that different instances can modify it separately. My understanding is that using jQuery or Zepto's $.extend is a great way to do 'copy by value'. It is not working in my situation and I'd like to understand why. I can't tell what I'm doing wrong.

var c = [ {'purple' : 1}, { 'red':2 } ]
var x = { 'name': 'my obj', 'colors': c }
var doWork = function(passX) {
  var inY = $.extend({},passX);
  inY.colors[0].selected = true;
  return inY;
}
var y = doWork(x);

console.log( x.colors[0].selected );// true, but I think it should be undefined
console.log( y.colors[0].selected );​// true

I feel like I might be missing something really central. Can I not extend from the same object to do a copy? Is there something confounding about the function argument scope?

jsFiddle: http://jsfiddle.net/zfnyp/5/

EDIT: The answer to my confusion, as @Niko points out, is that deep copy makes copy-by-value versions of all child properties. I thought deep vs shallow copy just meant how many properties deep the copy went. Oops.

EDIT AGAIN: Deep copy is troublesome in Javascript. JQuery has it, but Zepto and Underscore do not. Some describe it as impossible to implement well. To implement this for my problem, I have created this solution which relies on knowing the structure of the object. I believe this is the correct answer for me, though it is clunky.

var c = [ {'purple' : 1, 'selected':false }, { 'red':2 } ]
var x = { 'name': 'my obj', 'colors': c }
var doWork = function(passX) {
  var inY = $.extend({},passX);
  inY.colors = $.extend([], passX.colors);
  for (var i = 0; i < passX.colors.length; i++) {
    inY.colors[i] = $.extend({}, passX.colors[i]);
  }
  inY.colors[0].selected = true;
  return inY;
}
var y = doWork(x);

console.log( x.colors[0].selected );
console.log( y.colors[0].selected );

Upvotes: 3

Views: 2035

Answers (5)

Niko
Niko

Reputation: 26730

x = y makes x and y reference the same object, therefore y.colors also overwrites x.colors - the $.extend() is pretty useless in this case.

var x = { 'name': 'my obj', 'colors': c };
var y = $.extend(true, {}, x); // <-- first param "true" = make deep copy,
                               //     because otherwise "x.colors === y.colors"

The deep copy is required, because an array is also an object and therefore copied by reference when doing a shallow copy.

Or with the new code:

var inY = $.extend(true, {}, passX); // deep copy = also copy mod.colors by value

Upvotes: 4

Gerardo Lima
Gerardo Lima

Reputation: 6703

The method $.extend can perform a deep copy. All you have to do is add a new parameter in the first position as true -- it flags the deep copy mode -- and shift the remaining parameters right.

var o1 = {"a": "AA", "n": 11, "o": {"k0": "v0", "k1": "v1"}};
var oz = $.extend(true, {}, o1)
oz.o.k0 = 99;

dump(o1); // {a:"AA", n:99, o:{k0:"v0", k1:"v1"}}
dump(oz); // {a:"AA", n:99, o:{k0:99, k1:"v1"}}

Upvotes: 0

Bergi
Bergi

Reputation: 664620

No, that is because the variables x and y point to the same object. By setting y.colors to a new value, you will get that when accessing x.colors. That $.extend([],x.colors) is a copy of the original c does not matter here.

But it even would not work if x and y were different objects with different colors arrays. Using

var purple = {purple: 1}, red = {red: 2};
var colors = [purple, red];
var copy = $.extend([], colors);

then copy[0] would still point to purple as just as colors[0] does. You might want to use the deep option:

var x = {name:'my obj', colors:[{purple: 1}, {red:2}]};
var y = $.extend(true, {}, x);

Upvotes: 1

gen_Eric
gen_Eric

Reputation: 227270

It's not the copy that's causing this, it's the var x = y = {. You're setting y to an object, then setting x to y. That causes x and y to point to the same object.

You need to set the object to one of the variables, then copy it to the other.

var x = { 'name': 'my obj', 'colors': c };
var y = $.extend({}, x);

Upvotes: 1

qwertymk
qwertymk

Reputation: 35284

You want something like this:

var c = [ {'purple' : 1}, { 'red':2 } ]
var x = { 'name': 'my obj', 'colors': c };
var y = { 'name': 'my obj', 'colors': c };

y.colors = $.extend([],x.colors);

x.colors[0].selected = true;

console.log( x.colors[0].selected ); // true
console.log( y.colors[0].selected ); // now it's an error because y.colors[0] is undefined

DEMO

Upvotes: 1

Related Questions