176

I am trying to remove an element in an array in a forEach loop, but am having trouble with the standard solutions I've seen.

This is what I'm currently trying:

review.forEach(function(p){
   if(p === '\u2022 \u2022 \u2022'){
      console.log('YippeeeE!!!!!!!!!!!!!!!!')
      review.splice(p, 1);
   }
});

I know it's getting into the if because I'm seeing YippeeeeeE!!!!!!!!!!!!! in the console.

MY PROBLEM: I know that my for loop and if logic are sound, but my attempt to remove the current element from the array is failing.

UPDATE:

Tried out Xotic750's answer, and the element is still not being removed:

Here is the function in my code:

review.forEach(function (item, index, object) {
    if (item === '\u2022 \u2022 \u2022') {
       console.log('YippeeeE!!!!!!!!!!!!!!!!')
       object.splice(index, 1);
    }
    console.log('[' + item + ']');
});

Here is the output where the array is still not removed:

[Scott McNeil]
[reviewed 4 months ago]
[ Mitsubishi is AMAZING!!!]
YippeeeE!!!!!!!!!!!!!!!!
[• • •]

So obviously it is going into the if statement as directed, but it's also obvious that the [• • •] is still there.

7
  • 13
    Is there a reason you are using forEach? If you want to remove items, the most appropriate function is filter. Commented Jul 17, 2014 at 20:31
  • 3
    Not if you need to keep the reference to the original array. Commented Jul 17, 2014 at 20:35
  • Yes, we'd like to keep the reference to the original array. Commented Jul 17, 2014 at 20:40
  • It is not clear from your question, what is the actual problem that you are having? Can you give an example, perhaps a jsFiddle? It seems that you should perhaps be using the index attribute rather than item for your splice Commented Jul 17, 2014 at 20:42
  • @Xotic750 Sorry, added clarification. Commented Jul 17, 2014 at 20:44

9 Answers 9

365

It looks like you are trying to do this?

Iterate and mutate an array using Array.prototype.splice

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'b', 'c', 'b', 'a'];

review.forEach(function(item, index, object) {
  if (item === 'a') {
    object.splice(index, 1);
  }
});

log(review);
<pre id="out"></pre>

Which works fine for simple case where you do not have 2 of the same values as adjacent array items, other wise you have this problem.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

review.forEach(function(item, index, object) {
  if (item === 'a') {
    object.splice(index, 1);
  }
});

log(review);
<pre id="out"></pre>

So what can we do about this problem when iterating and mutating an array? Well the usual solution is to work in reverse. Using ES3 while but you could use for sugar if preferred

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a' ,'a', 'b', 'c', 'b', 'a', 'a'],
  index = review.length - 1;

while (index >= 0) {
  if (review[index] === 'a') {
    review.splice(index, 1);
  }

  index -= 1;
}

log(review);
<pre id="out"></pre>

Ok, but you wanted to use ES5 iteration methods. Well and option would be to use Array.prototype.filter but this does not mutate the original array but creates a new one, so while you can get the correct answer it is not what you appear to have specified.

We could also use ES5 Array.prototype.reduceRight, not for its reducing property by rather its iteration property, i.e. iterate in reverse.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

review.reduceRight(function(acc, item, index, object) {
  if (item === 'a') {
    object.splice(index, 1);
  }
}, []);

log(review);
<pre id="out"></pre>

Or we could use ES5 Array.protoype.indexOf like so.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'],
  index = review.indexOf('a');

while (index !== -1) {
  review.splice(index, 1);
  index = review.indexOf('a');
}

log(review);
<pre id="out"></pre>

But you specifically want to use ES5 Array.prototype.forEach, so what can we do? Well we need to use Array.prototype.slice to make a shallow copy of the array and Array.prototype.reverse so we can work in reverse to mutate the original array.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

review.slice().reverse().forEach(function(item, index, object) {
  if (item === 'a') {
    review.splice(object.length - 1 - index, 1);
  }
});

log(review);
<pre id="out"></pre>

Finally ES6 offers us some further alternatives, where we do not need to make shallow copies and reverse them. Notably we can use Generators and Iterators. However support is fairly low at present.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

function* reverseKeys(arr) {
  var key = arr.length - 1;

  while (key >= 0) {
    yield key;
    key -= 1;
  }
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

for (var index of reverseKeys(review)) {
  if (review[index] === 'a') {
    review.splice(index, 1);
  }
}

log(review);
<pre id="out"></pre>

Something to note in all of the above is that, if you were stripping NaN from the array then comparing with equals is not going to work because in Javascript NaN === NaN is false. But we are going to ignore that in the solutions as it it yet another unspecified edge case.

So there we have it, a more complete answer with solutions that still have edge cases. The very first code example is still correct but as stated, it is not without issues.

Sign up to request clarification or add additional context in comments.

10 Comments

Thanks for answering. I tried using your solution, but it still isn't removing the element from the array. I'll put up the details in the question.
Put console.log(review); after the forEach, like in my example.
Careful, this breaks if two consecutive elements to delete: var review = ['a', 'a', 'c', 'b', 'a']; will yield ['a', 'c', 'b']
NOTE - This answer is WRONG! The foreach iterate through the array by index. Once you delete elements while iterating the index of following items changes. In this example, once you delete the first 'a', index number 1 now becomes 'c'. Therefore the first 'b' is not even evaluated. Since you didn't try to delete it, it just happened to be ok, but that is not the way. You should iterate through a reverse copy of the array, and then delete items in the original array.
@Xotic750 - the original answer (now being the first code snippet) is wrong simply because the forEach will not loop through all elements in the array, as I explained in the previous comment. I know that the question was how to remove elements in a forEach loop, but the simple answer is that you don't do that. Since many people are reading those answers, and many times blindly copying answers (especially accepted answers), it is important to note flaws in the code. I think that the reverse while loop is the simplest, most efficient and most readable solution and should be the accepted answer
|
57

Use Array.prototype.filter instead of forEach:

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'b', 'c', 'b', 'a', 'e'];
review = review.filter(item => item !== 'a');
log(review);

Comments

48

Although Xotic750's answer provides several good points and possible solutions, sometimes simple is better.

You know the array being iterated on is being mutated in the iteration itself (i.e. removing an item => index changes), thus the simplest logic is to go backwards in an old fashioned for (à la C language):

let arr = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

for (let i = arr.length - 1; i >= 0; i--) {
  if (arr[i] === 'a') {
    arr.splice(i, 1);
  }
}

document.body.append(arr.join());

If you really think about it, a forEach is just syntactic sugar for a for loop... So if it's not helping you, just please stop breaking your head against it.

1 Comment

This is the best answer. No heroics. Just gets the job done! Nice work.
2

I understood that you want to remove from the array using a condition and have another array that has items removed from the array. Is right?

How about this?

var review = ['a', 'b', 'c', 'ab', 'bc'];
var filtered = [];
for(var i=0; i < review.length;) {
  if(review[i].charAt(0) == 'a') {
    filtered.push(review.splice(i,1)[0]);
  }else{
    i++;
  }
}

console.log("review", review);
console.log("filtered", filtered);

Hope this help...

By the way, I compared 'for-loop' to 'forEach'.

If remove in case a string contains 'f', a result is different.

var review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
var filtered = [];
for(var i=0; i < review.length;) {
  if( review[i].includes('f')) {
    filtered.push(review.splice(i,1)[0]);
  }else {
    i++;
  }
}
console.log("review", review);
console.log("filtered", filtered);
/**
 * review [  "concat",  "copyWithin",  "entries",  "every",  "includes",  "join",  "keys",  "map",  "pop",  "push",  "reduce",  "reduceRight",  "reverse",  "slice",  "some",  "sort",  "splice",  "toLocaleString",  "toSource",  "toString",  "values"] 
 */

console.log("========================================================");
review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
filtered = [];

review.forEach(function(item,i, object) {
  if( item.includes('f')) {
    filtered.push(object.splice(i,1)[0]);
  }
});

console.log("-----------------------------------------");
console.log("review", review);
console.log("filtered", filtered);

/**
 * review [  "concat",  "copyWithin",  "entries",  "every",  "filter",  "findIndex",  "flatten",  "includes",  "join",  "keys",  "map",  "pop",  "push",  "reduce",  "reduceRight",  "reverse",  "slice",  "some",  "sort",  "splice",  "toLocaleString",  "toSource",  "toString",  "values"]
 */

And remove by each iteration, also a result is different.

var review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
var filtered = [];
for(var i=0; i < review.length;) {
  filtered.push(review.splice(i,1)[0]);
}
console.log("review", review);
console.log("filtered", filtered);
console.log("========================================================");
review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
filtered = [];

review.forEach(function(item,i, object) {
  filtered.push(object.splice(i,1)[0]);
});

console.log("-----------------------------------------");
console.log("review", review);
console.log("filtered", filtered);

Comments

1

You could also use indexOf instead to do this

var i = review.indexOf('\u2022 \u2022 \u2022');
if (i !== -1) review.splice(i,1);

Comments

0

The following will give you all the elements which is not equal to your special characters!

review = jQuery.grep( review, function ( value ) {
    return ( value !== '\u2022 \u2022 \u2022' );
} );

Comments

0

I had problems with the following code. My solution follows after. First, the problem: Let's assume you want to trim strings, and discard the string with a 'c' in it:

var review = ['    a      ', '   b   ', '   c   ', '   d   ', '   e   '];

review.forEach(function(item, index) {
   review[index] = item.trim();
   console.log("After trimming, the item is ", review[index]);
  if (review[index] === 'c') {
    review.splice(index, 1);
  }
});
console.log("Review is now: ");
console.log(review);

If you run the above piece of code, you will see that ' d ' is never trimmed. It remains in the review array, with spaces before and after it.

enter image description here

That's because you are messing with the review array that the foreach is built on. It's a lot better to make a copy of the array, and push elements into it that are OK to keep, and skip the ones that are not. Then, output the new FinalArray, like this:

var review = ['    a      ', '   b   ', '   c   ', '   d   ', '   e   '];
var finalArray = [];

review.forEach(function(item, index) {
    // Add it to the new array first.  If it turns out to be bad, remove it before going onto 
    // the next iteration.
   finalArray.push(item.trim());
   console.log("After trimming, the item is ", item.trim());
  if (item.trim() == 'c') { // skip c
    finalArray.pop();     // This way, removing it doesn't affect the original array.
  }
});
console.log("FinalArray is now: ");
console.log(finalArray);

As you can see, this worked perfectly:

enter image description here

Comments

0

You could also store a list of items to delete while looping and delete them after

const toDelete = [];
review.forEach((p, i) => {
   if (p === '\u2022 \u2022 \u2022') {
     toDelete.push(i);
   }
});
// We substract j because the index moves every time we remove an item
toDelete.forEach((i, j) => pages.splice(i - j, 1));

It might not be the most efficient method, but it's a good one if you already have a forEach for other stuff and want to delete items in the same iteration

Comments

-1

Here is how you should do it:

review.forEach(function(p,index,object){
   if(review[index] === '\u2022 \u2022 \u2022'){
      console.log('YippeeeE!!!!!!!!!!!!!!!!')
      review.splice(index, 1);
   }
});

3 Comments

I don't think is the case. I changed my code assuming p was an index, and now it's not even getting into the if statement.
@WhoCares You should see the spec ecma-international.org/ecma-262/5.1/#sec-15.4.4.18 The callBack function arguments are item, index, object
This will mutate the array while it is looping and skip over values to delete (and it was already the problem op had)

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.