admin管理员组

文章数量:1313385

I ran across this strange side effect of array.splice, and distilled the code down to the minimum necessary to recreate. Yes, much of this could be done on one line with array.filter, but I'm interested in whether I've made a mistake or if something else is going on.

var array = [];

for (var i = 0; i < 10; i++) {
  array.push({
    value: i
  });
}

array.forEach(function(item, index, intArray) {
  if (item.value % 2 == 1) {
    item.odd = true;
  } else {
    item.odd = false;
  }

  if (item.odd) {
    console.log("Removing " + item.value);
    intArray.splice(index, 1);
  }

});

console.log(array);

I ran across this strange side effect of array.splice, and distilled the code down to the minimum necessary to recreate. Yes, much of this could be done on one line with array.filter, but I'm interested in whether I've made a mistake or if something else is going on.

var array = [];

for (var i = 0; i < 10; i++) {
  array.push({
    value: i
  });
}

array.forEach(function(item, index, intArray) {
  if (item.value % 2 == 1) {
    item.odd = true;
  } else {
    item.odd = false;
  }

  if (item.odd) {
    console.log("Removing " + item.value);
    intArray.splice(index, 1);
  }

});

console.log(array);

Running this javascript results in the odd elements being removed as expected, but it also removed the item.odd values for items 2, 4, 6, and 8. Removing the intArray.splice line brings back the odd array elements, but it also brings back the item.odd values for all elements.

I've tested this in FF and Chrome. The behavior persists even if only the item is passed into the callback, with the index calculated via array.indexOf, and referencing the array from outside the loop.

Share Improve this question edited Jan 21, 2016 at 19:35 RisingSun 1,73327 silver badges46 bronze badges asked Jan 21, 2016 at 19:25 Brian BaumanBrian Bauman 6685 silver badges22 bronze badges 2
  • 3 I wonder if there's some issue with the array being edited while you're looping over it. Like maybe the index value is the index in the original array and not in the newly modified one. – gen_Eric Commented Jan 21, 2016 at 19:30
  • I had similar thoughts. However, I'd expect the bug in that case to be items and indexes not matching as expected, so too many or too few elements being removed, rather than values within elements disappearing. Plus, if that were the case I'd be on the lookout for a difference between passing index into the callback and calculating it on-the-fly - but they work out the same in practice. – Brian Bauman Commented Jan 21, 2016 at 19:32
Add a ment  | 

2 Answers 2

Reset to default 9

I think that when you splice the array at every odd number, the forEach ends up skipping over the next item, which is an even number. So those items don't get modified at all.

var array = [];

for (var i = 0; i < 10; i++) {
  array.push({
    value: i
  });
}

array.forEach(function(item, index, intArray) {
  console.log(item); // only prints out 0, 1, 3, 5, 7, 9

  if (item.value % 2 == 1) {
    item.odd = true;
  } else {
    item.odd = false;
  }

  if (item.odd) {
    console.log("Removing " + item.value);
    intArray.splice(index, 1);
  }

});

console.log(array);

In other words, forEach only visits each index once. So say it gets to item 1, which is at index 1. It deletes item 1. Item 2 is now at index 1. But index 1 has already been visited, so it moves on to the item at index 2, which is now item 3.

Since splice is destructive and forEach is not 'live' [it is not automatically updated], the iteration after splice( index, 1 ) will skip over the "old index+1" (the next iteration-index) because it has 'dropped' down to the new index (where the old array element used to be). Using splice to delete array elements (no matter how many elements are deleted) will always create this unexpected 'slippage' from the old index to the new index, and will skip over one array element every time splice is used.

When using splice in a loop to delete array elements I use a for loop with negative iteration. When the array slips down, the for loop will not skip over it because it drops down to the next (negative) iteration:

for ( let i = array.length - 1; i >= 0; i-- ){
  // do destructive stuff with splice
}

=> start at the end of the array (don't forget to -1, since arrays are zero-based); continue while i >= 0; and decrement i at each iteration.

本文标签: javascriptarraysplice removing values from remaining elementsStack Overflow