Reputation: 55293
I have an array called this.list
. I wanted to map through it's items and give a new value to those items:
this.list = this.list.map(item => {
if (item.id === target.id) {
item.dataX = parseFloat(target.getAttribute('data-x'))
item.dataY = parseFloat(target.getAttribute('data-y'))
}
return item
})
But to my surprise, this also worked:
this.list.map(item => {
if (item.id === target.id) {
item.dataX = parseFloat(target.getAttribute('data-x'))
item.dataY = parseFloat(target.getAttribute('data-y'))
}
return item
})
How come map is assigning a new value to this.json
if I'm not using any assignment at all?
Upvotes: 1
Views: 57
Reputation: 6414
Based upon your example, comments inline:
// The function map returns a _new_ array
// containing all of the existing elements.
// Then is assigned to the existing variable
// overwriting the original array.
this.list = this.list.map(item => {
// Here "item" refers to the actual object element in the
// existing list any assignments to this object will affect
// the existing item in the list.
if (item.id === target.id) {
// Here are your assigments.
item.dataX = parseFloat(target.getAttribute('data-x'));
item.dataY = parseFloat(target.getAttribute('data-y'));
}
// Although the item is returned and would replace the
// existing element in the new resulting list, the item
// returned is in fact the actual element that is _already_
// currently in the list.
return item;
});
Therefore in this case the following is equivalent in terms of result based upon the fact you are assigning to the original variable and updating in place the original values:
this.list.forEach(item => {
if (item.id === target.id) {
item.dataX = parseFloat(target.getAttribute('data-x'));
item.dataY = parseFloat(target.getAttribute('data-y'));
}
});
To perform this in a way that doesn't mutate the original objects, if that is what you desire:
var result = this.list.map(item => {
return target.id === item.id ? {
id: item.id,
dataX: parseFloat(target.getAttribute('data-x')),
dataY: parseFloat(target.getAttribute('data-y'))
} : item;
});
Or if item
has additional data that you need to carry across you may need to somehow copy the object: How do I correctly clone a JavaScript object?. One alternative is to model the types around this, for example:
class Item {
constructor(id, x, y) {
this.id = id;
this.dataX = dataX;
this.dataY = dataY;
}
// Immutability style setter, returns the result
// as a new instance.
withData(dataX, dataY) {
return new Item(this.id, dataX, dataY);
}
}
var result = this.list.map(item => {
return item.id === target.id ? item.withData(
parseFloat(target.getAttribute('data-x')),
parseFloat(target.getAttribute('data-y'))
) : item;
});
In the above example this.list
is the untouched original array containing all of the elements as they were prior to the map operation.
result
contains a mix of "updated" elements (new instances of the elements that that matched target.id
and original items that didn't.
If we were to number all of the instances, before the map operation in the first example using map
and assigning back to this.list
we might have:
this.list
Array 1
- Item 1
- Item 2
- Item 3
- Item 4
After the map operation, the items are the same instances, and have been updated, but the array is a different instance:
this.list -> map -> this.list
Array 1 Array 2 (NEW)
- Item 1 -> - Item 1
- Item 2 -> - Item 2
- Item 3 -> - Item 3
- Item 4 -> - Item 4
In the forEach
example, both before the forEach
operation and after the result is the same, the instances have been updated in place:
this.list (forEach) this.list (No change)
Array 1 Array 1
- Item 1 - Item 1
- Item 2 - Item 2
- Item 3 - Item 3
- Item 4 - Item 4
In each of the immutable examples this.list
is the same as before but result
will be a different array instance and the matched items will be different instances, in the following example Item 1
was matched and updated:
this.list -> map -> result this.list (Untouched)
Array 1 Array 2 (NEW) Array 1
- Item 1 - Item 5 (NEW) - Item 1
- Item 2 -> - Item 2 - Item 2
- Item 3 -> - Item 3 - Item 3
- Item 4 -> - Item 4 - Item 4
Upvotes: 1
Reputation: 5524
JavaScript stores objects as references so when you map over an array, you are changing the original object. You can see the same behaviour in the code bellow:
let original = [{x: 5}];
let copied = original;
copied[0].x = 6;
console.log(original[0].x); // 6
You would have to re-create the object or clone it somehow.
this.list = this.list.map(item => {
if (item.id === target.id) {
item = {
id: target.id,
dataX: parseFloat(target.getAttribute('data-x')),
dataY: parseFloat(target.getAttribute('data-y')),
...
};
}
return item
})
Upvotes: 1
Reputation: 3940
Refering to the map
function docs:
map does not mutate the array on which it is called (although callback, if invoked, may do so).
Note that the item
object you're using in the callback
is a reference to the actual this.list
element and in your case you're mutating the item
object.
Upvotes: 1