delsin
delsin

Reputation: 323

Javascript object assignment's strange behavior

I am new to javascript (new to programming overall, really). And I encountered this behavior of for/in loop that I don't quite understand. The following pieces of code were run with $node command in console.

code_0:

var result = {};
var list = ['A', 'B', 'C'];

for(var index in list){
    var id = list[index];
    result[id] = {};
    result[id]['name'] = id;
}

console.log(result);

result_0:

{ A: { name: 'A' }, B: { name: 'B' }, C: { name: 'C' } }

code_1:

var result = {};
var list = ['A', 'B', 'C'];
var INIT = {'a': 0, 'b': 0, 'c': 0,}

for(var index in list){
    var id = list[index];
    result[id] = INIT;
    result[id]['name'] = id;
}

console.log(result);

result_1:

{ A: { a: 0, b: 0, c: 0, name: 'C' },
  B: { a: 0, b: 0, c: 0, name: 'C' },
  C: { a: 0, b: 0, c: 0, name: 'C' } }

I can understand why code_0 would give result_0. But result_1 is what I don't understand. I expected result_1 to be:

{ A: { a: 0, b: 0, c: 0, name: 'A' },
  B: { a: 0, b: 0, c: 0, name: 'B' },
  C: { a: 0, b: 0, c: 0, name: 'C' } }

What's the difference between code_0 and code_1? Why would code_1 give result_1?

EDIT: * Add tags related to the question. * Change title. (Title used to be about for/in loops)

Upvotes: 2

Views: 128

Answers (1)

jfriend00
jfriend00

Reputation: 707318

Objects are assigned by reference (not by copy) in Javascript. This is a common point of confusing and learning when first getting up to speed in Javascript.

In your code_1 block, this line of code:

result[id] = INIT;

is assigning a reference to the exact same object in every iteration of your loop, thus they all point to the same object and will all have the same properties (since they are all the same object). So, any further changes to result[id] are really just changes to INIT which is the same object that all the slots in result are using so they all appear to change at once.

In Javascript, if you want a separate copy of an object in each assignment, then you have to create a new object before assigning. In the latest browsers, you can use Object.assign() to copy enumerable properties from one object to another.

Using Object.assign(), here's one way around the issue that makes a copy of the object before assigning it:

var result = {};
var list = ['A', 'B', 'C'];
var INIT = {'a': 0, 'b': 0, 'c': 0,}

for(var index in list){
    var id = list[index];
    // make a new object that is a copy of INIT
    var obj = Object.assign({}, INIT);
    obj.name = id;
    // put that new object into result[id]
    result[id] = obj;
}

console.log(result);

For older browsers, there's a polyfill for Object.assign() here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


Here's a little simpler example:

 var items = {a:1, b:2, c:3};

 var list1 = items;
 var list2 = items;

 list1.a = 10;
 console.log(items);               // {a:10, b:2, c:3}
 console.log(list1);               // {a:10, b:2, c:3}
 console.log(list2);               // {a:10, b:2, c:3}
 console.log(list1 === items);     // true, they are the same object
 console.log(list2 === items);     // true, they are the same object

You can see that all three variables show the same values because they all point to the exact same object so modifying the object via any one of the three variables make the exact same change.

Upvotes: 6

Related Questions