Home > Net >  JS forEach traverses the problem of modifying objects
JS forEach traverses the problem of modifying objects

Time:06-16

Why does the first modification work (when I reassign properties of the object) but not the second (when I reassign the whole object)?

const arr1 = [
    { id: 1, value: 1 },
    { id: 2, value: 2 },
    { id: 3, value: 3 },
    { id: 4, value: 4 },
    { id: 5, value: 5 },
]

arr1.forEach((item, index) => {
    if (item.id === 1) {
        item.value *= 10   // modify success
    }
});

console.log(arr1);

arr1.forEach((item, index) => {
    if (item.id === 1) {
        item = {id:6,value:6}  // modify fail
    }
});

console.log(arr1);
.as-console-wrapper { max-height: 100% !important; top: auto; }

CodePudding user response:

Your confusion is natural, and it's to do with the difference between the actual object, and the variable.

In your foreach function, you have a variable "item" which points to the object in the array you are looping. But when you run

item = {id:6,value:6}

You are not modifying the object, but rather making the variable "item" point to a new object that you've just created.

If you want to change the actual object itself, you can do it manually by modifying the values one by one, or by copying from another object using Object.assign.

enter image description here

Next let's change the val field of the object i.e. assigned to bar. We notice that the change is reflected by both the variables foo and bar and this is because both the variables refer to the same object.

let foo = {id: 1,val: "foo"};
let bar = foo;
bar.val = "bar";
console.log(foo, bar);

enter image description here

Next we assign a new object to bar. Notice this doesn't effect the object that foo refers to, bar is simply now referring to a different object.

let foo = { id: 1, val: "foo" };
let bar = foo;
bar = { id: 1, val: "bar" };
console.log(foo, bar);

enter image description here

Let's relate this to the forEach example in your question. So, in every iteration of the forEach loop, the item argument in the callback function points to an object from the array and when you change a field from this item argument it changes the object in the array but when you assign item to a new object it does nothing to the object stored in the array.

If you want to replace the entire object with a new one, there are several approaches you could take, two of which are mentioned below:

  1. Finding the index where the object is stored and replacing it with a new object.

const arr = [{ id: 1, value: 1 }, { id: 2, value: 2 }, { id: 3, value: 3 }, { id: 4, value: 4 }, { id: 5, value: 5 }];

const index = arr.findIndex((obj) => obj.id === 3);
if (index !== -1) {
  arr[index] = { id: 6, value: 6 };
}

console.log(arr);

  1. Another really common approach is to map over the array and create a new array with that one object replaced.

const arr = [{ id: 1, value: 1 }, { id: 2, value: 2 }, { id: 3, value: 3 }, { id: 4, value: 4 }, { id: 5, value: 5 }];

const newArr = arr.map((obj) => (obj.id === 3 ? { id: 6, value: 6 } : obj));

console.log(newArr);

CodePudding user response:

Because item is a reference to the object that's currently being iterated over, modifying the reference will in turn modify the original object (hence the behaviour observed in your first code example). In the second forEach you just reassign the reference to a brand new object so you can no longer modify the original object you were iterating over.

Basically, when you loop over the array with forEach, item is a variable whose current value is a reference/pointer to the object currently being iterated over. Modifying this object modifies the original (as it's just a reference, not an actual cloned object). However, when you reassign it, the reference is no longer present because item has a new value that it points to. You could modify it like so, with success:

const arr1 = [
    { id: 1, value: 1 },
    { id: 2, value: 2 },
    { id: 3, value: 3 },
    { id: 4, value: 4 },
    { id: 5, value: 5 },
];

arr1.forEach((item, index) => {
    if (item.id === 1) {
        item.value *= 10   // modify success
    }
});

console.log(arr1);

arr1.forEach((item, index) => {
    if (item.id === 1) {
        item.id = 6;
        item.value = 6;
    }
});

console.log(arr1);
.as-console-wrapper { max-height: 100% !important; top: auto; }

You could do it dynamically as well, by looping over each property you wish to reassign like so:

const arr1 = [
    { id: 1, value: 1 },
    { id: 2, value: 2 },
    { id: 3, value: 3 },
    { id: 4, value: 4 },
    { id: 5, value: 5 },
];

arr1.forEach((item, index) => {
    if (item.id === 1) {
        item.value *= 10   // modify success
    }
});

console.log(arr1);

arr1.forEach((item, index) => {
    if (item.id === 1) {
        let toConvert = { id: 6, value: 6};
        Object.entries(toConvert).forEach(([k, v]) => item[k] = v);
    }
});

console.log(arr1);
.as-console-wrapper { max-height: 100% !important; top: auto; }

CodePudding user response:

For the first case, it can be understood as:

const it = { value: 10 };
const item = it;
item.value = 11;
console.log(it);   // item and it point to the same object, so the object's property value is modified

second case:

const it = { value: 10 };
let item = it;           // Here item and it refer to the same object
item = { value: 6 };     // But here item points to the new object (direct assignment), and it still points to the original object
console.log(item, it);   // So it is still { value: 10 }, and item is already { value: 6 }
  • Related