35

I have this code which is supposed to iterate over each item in an array, removing items based on some condition:

//iterate over all items in an array
//if the item is "b", remove it.

var array = ["a", "b", "c"];

array.forEach(function(item) {
  if(item === "b") {
    array.splice(array.indexOf(item), 1);
  }

  console.log(item);
});

Desired output:

a
b
c

Actual output:

a
b

Obviously the native forEach method doesn't check after each iteration whether the item has been deleted, so if it is then the next item is skipped. Is there a better way of doing this, aside from overriding the forEach method or implementing my own class to use instead of an array?

Edit - further to my comment, I suppose the solution is to just use a standard for loop. Feel free to answer if you have a better way.

1
  • Actually I can see why they wouldn't try to support this functionality - you would need to check whether the deleted item was the current item, otherwise you might skip backwards unnecessarily (e.g. if the check was just based on whether the length had reduced by one or something). seems too complicated to implement. Commented Feb 16, 2014 at 13:25

5 Answers 5

56

Lets see why JavaScript behaves like this. According to the ECMAScript standard specification for Array.prototype.forEach,

when you delete an element at index 1, the element at index 2 becomes the element at index 1 and index 2 doesn't exist for that object.

Now, JavaScript looks for element 2 in the object, which is not found, so it skips the function call.

That is why you see only a and b.


The actual way to do this, is to use Array.prototype.filter

var array = ["a", "b", "c"];

array = array.filter(function(currentChar) {
    console.log(currentChar);   // a, b, c on separate lines
    return currentChar !== "b";
});
console.log(array);             // [ 'a', 'c' ]
Sign up to request clarification or add additional context in comments.

5 Comments

+1 but, if you need to make any change to the array item (as I did in my circumstance - array of objects), as well as filter out items, best to use reduce.
? you cannot log anything to the console after a return statement
@Blauhirn No, you can't.
@JuanBiscaia that is my point. In the above example, there is a logging statement after the return.
@Blauhirn oh, ok, sorry, I was in a hurry and didn't notice you aren't asking a question, my bad.
26

One possibility would be to use the array.slice(0) function, which creates a copy (clone) of the array and thus the iteration is separated from the deletion.

Then the only change to the original approach using array.forEach would be to change it to array.slice(0).forEach and it will work:

array.slice(0).forEach(function(item) {
    if(item === "b") {
        array.splice(array.indexOf(item), 1);
    }
    alert(item)
});

After the forEach, the array will contain only a and c.

A jsFiddle demo can be found here.

4 Comments

Also if you use lodash/underscore, you could do _.clone(array).forEach(function (item) { ... })
This is interesting, but it involves a lookup on each iteration, though the searched array diminishes in size. It would be faster to write a for loop and mind your index.
forEach has an index already. no need for indexOf
@Curcuma_ at this case using the forEach index will not work try it for yourself.
6

Using Array.prototype.filter as in thefourtheye's answer is a good way to go, but this could also be done with a while loop. E.g.:

const array = ["a", "b", "c"];
let i = 0;

while (i < array.length) {
    const item = array[i];

    if (item === "b") {
        array.splice(i, 1);
    } else {
        i += 1;
    }

    console.log(item);
}

2 Comments

end block of while have only closing curly brace" } " its should not " }); " by applying this minor changes, code will compile successfully
Very true @AvnishPatel, good catch! Fixed.
4

Another possibility would be to use the array.reduceRight function to avoid the skip:

//iterate over all items in an array from right to left
//if the item is "b", remove it.

const array = ["a", "b", "c"];

array.reduceRight((_, item, i) => {
    if(item === "b") {
        array.splice(i, 1);
    }
}, null);

console.log(array);

After the reduceRight, the array will contain only a and c.

2 Comments

If used like this, it ignores the item at the last position. The accumulator (defined as _ ) contains the first object from the array if no initialValue is passed. You need to pass a 2nd argument (initialValue) to array.reduceRight(callback, initialValue).
@Christopher Thanks! If no initial value is supplied, the last element in the array will be used and skipped.
0
const array = ["a", "b", "c"];
let i = array.length;
while (i--) {
    if (array[i] === "b") {
       array.splice(i, 1);
       break;
    }
}
console.log(item);

1 Comment

While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.