如何在forEach循环中从数组中删除元素?

163

我试图在使用 forEach 循环的数组中删除一个元素,但是我遇到了一些标准解决方案上的问题。

这是我目前尝试的代码:

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

我知道它进入了if,因为我在控制台中看到了YippeeeeeE!!!!!!!!!!!!!

我的问题:我知道我的for循环和if逻辑是正确的,但我试图从数组中删除当前元素的尝试失败了。

更新:

尝试了Xotic750的答案,但仍未删除该元素:

这是我代码中的函数:

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

这里是输出,数组仍未被删除:

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

显然,它按照指示进入了if语句,但很明显[• • •]仍然存在。


12
你为什么使用forEach?如果你要移除项目,最合适的函数是filter。请注意不要改变原意,并使翻译更加通俗易懂。 - Jon
3
如果需要保留对原始数组的引用,就不能这样做。 - Xotic750
是的,我们想保留对原始数组的引用。 - novicePrgrmr
@Xotic750 抱歉,我添加了澄清。 - novicePrgrmr
可能是重复的问题:如何使用forEach从列表中删除元素? - Armfoot
显示剩余2条评论
8个回答

352

看起来你正在尝试做这个?

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>

这对于简单情况是有效的,即相邻数组项中没有两个相同的值,否则就会出现问题。

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>

当迭代和改变数组时,我们可以怎么做来解决这个问题呢?通常的解决方案是反向操作。使用 ES3 的 while 循环,但如果更喜欢,也可以使用 for 循环语法糖。

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>

好的,但您想使用ES5迭代方法。一个选择是使用Array.prototype.filter,但这不会改变原始数组而是创建一个新数组,所以虽然您可以得到正确的答案,但这不是您似乎指定的。

我们也可以使用ES5 Array.prototype.reduceRight,而不是它的减少属性,而是它的迭代属性,即反向迭代。

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>

或者我们可以像这样使用 ES5 Array.protoype.indexOf

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>

但是你特别想使用ES5 Array.prototype.forEach,那么我们该怎么办?好的,我们需要使用Array.prototype.slice来制作数组的浅拷贝,并使用Array.prototype.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.slice().reverse().forEach(function(item, index, object) {
  if (item === 'a') {
    review.splice(object.length - 1 - index, 1);
  }
});

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

最终ES6为我们提供了一些更进一步的替代方案,我们不需要制作浅层副本并将其反转。值得注意的是,我们可以使用生成器和迭代器。然而,目前支持程度相当低。

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>

需要翻译的内容:

需要注意的是,如果您从数组中删除NaN,然后进行比较,使用等号将不起作用,因为在Javascript中NaN === NaN是false。但是,在解决方案中,我们将忽略这一点,因为它是另一个未指定的边缘情况。

所以,我们有了更完整的答案和仍然存在边缘情况的解决方案。第一个代码示例仍然是正确的,但正如所述,它并非没有问题。


4
注意,如果要删除相邻的两个元素,以下代码将无法正常工作:var review = ['a', 'a', 'c', 'b', 'a']; 该代码将返回 ['a', 'c', 'b']。 - quentinadam
@goldmine,我也不会使用这种方法来完成这项工作,但问题并不在于这种方法是否适合手头的工作,而更多地是关于如何使用Array#spliceArray#forEach。你提醒人们用这种方式可能存在潜在问题是很好的,但我认为它并不应该被投票否决,因为它解决了OP所遇到的实际问题。 - Xotic750
5
注意 - 此答案是错误的!foreach循环是通过索引迭代数组的。一旦你在迭代时删除元素,后面项目的索引号码会发生变化。在这个例子中,一旦你删除了第一个'a',索引号1现在变成了'c'。因此,第一个'b'甚至没有被评估。由于你没有尝试删除它,它只是巧合地没问题,但这不是正确的方法。你应该通过原始数组的倒序副本进行迭代,然后删除原始数组中的项目。 - danbars
@user1655734 的回答并没有错,但是它没有指出任何潜在问题或边缘情况。我现在已经扩展了答案以提供更多细节。 - Xotic750
5
原始答案(现在是第一个代码片段)是错误的,因为forEach不会遍历数组中的所有元素,正如我在先前的评论中所解释的那样。我知道问题是如何在forEach循环中删除元素,但简单的答案是你不应该这么做。由于许多人正在阅读这些答案,并且许多时候盲目地复制答案(尤其是已接受的答案),因此重要的是指出代码中的缺陷。我认为反向while循环是最简单、最有效和最易读的解决方案,因此应该被接受的答案。 - danbars
显示剩余7条评论

56

使用Array.prototype.filter代替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);

太棒了 - Amin Joharinia

43
尽管Xotic750的回答提供了几个好点和可能的解决方案,有时简单就是更好。您知道正在迭代的数组在迭代本身中被改变(即删除一个项目=>索引更改),因此最简单的逻辑是倒退到老式的for(à la C语言):

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

如果你真的想想,一个forEach只是一个for循环的语法糖...所以如果它没有帮助到你,就请停止折磨自己。

1
这是最好的答案。没有任何英雄主义,只是完成了工作!干得好。 - monkey

2

我理解您想要使用条件从数组中删除并获得另一个已从数组中删除项目的数组。是这样吗?

这个怎么样?

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

希望这有所帮助...

顺便说一下,我比较了'for-loop'和'forEach'。

如果在一个字符串中删除包含'f'的部分,结果会有所不同。

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"]
 */

每次迭代都会删除,因此结果也不同。

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


1

你也可以使用indexOf来完成这个操作。

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

0

这是你应该做的:

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

1
我不认为是这种情况。我更改了我的代码,假设p是一个索引,现在它甚至没有进入“if”语句。 - novicePrgrmr
3
@WhoCares 你应该查看规范 http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18 回调函数的参数是 item, index, object - Xotic750

0
我遇到了以下代码问题。我的解决方案如下。首先,问题是:假设您想要修剪字符串,并且丢弃其中包含“c”的字符串:

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

如果运行上述代码,你会发现 ' d ' 从未被修剪。它仍然留在评论数组中,前后带有空格。

enter image description here

这是因为您正在干扰foreach所构建的评论数组。更好的方法是复制该数组,并将可以保留的元素推入其中,跳过不需要的元素。然后,像这样输出新的FinalArray:

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

正如您所看到的,这个工作非常完美:

enter image description here


0

以下将为您提供所有不等于特殊字符的元素!

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接