Splicing a Javascript array from within the callba

2019-01-12 02:25发布

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.

4条回答
别忘想泡老子
2楼-- · 2019-01-12 02:59

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 b.

A jsFiddle demo can be found here.

查看更多
Juvenile、少年°
3楼-- · 2019-01-12 03:11

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' ]
查看更多
Explosion°爆炸
4楼-- · 2019-01-12 03:15

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);
    }

});

console.log(array);

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

查看更多
【Aperson】
5楼-- · 2019-01-12 03:20

All the above mentioned answers just fail or not retain the original array to be passed elsewhere, if we were to remove two elements at a specific index and continue iterating from the immediate element. for suppose, I have an array

vehicles = [{make: ford, model: mustang}, 
            {make: chevy, model: camaro}, 
            {make: chevy, model: camaro},
            {make: ford, model: mustang},
            {make: chevy, model: camaro}]

I want to splice away two elements if there is successive combination of ford and chevy.

vehicles.forEach(function (vehicle) {
         if (vehicle) {
              var index = vehicles.indexOf(vehicle);
              var flag = vehicle.make=== "ford" && vehicles[index + 1].make=== "chevy";
              if (flag) {
                  //Array.Prototype.forEach() wouldn't update the iteration index after splice
                  vehicles.splice(index, 2, null);
              }
          }
});

So this way I am replacing the couple spliced elements with a null so that I could adapt to non updating iteration index of forEach(). Then I can clean the array of any inserted nulls once the iteration is complete, and array is ready to be handed off.

//After all the iteration is done, we clear all the inserted null
vehicles = [].concat(vehicles.filter(Boolean));

This can be a better way of not hampering anything and absoloutely solving this spooky behavior of javascript.

查看更多
登录 后发表回答