在forEach回调函数内部拼接JavaScript数组

32
我有这样的一段代码,它应该遍历数组中的每个项,并根据某些条件删除其中的项:

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

期望的输出:

a
b
c

实际输出:

a
b

显然,原生的forEach方法在每次迭代后不会检查项目是否已被删除,因此如果删除了该项目,则会跳过下一个项目。除了覆盖forEach方法或实现自己的类以代替数组之外,有没有更好的方法来解决这个问题?

编辑-根据我的评论,我认为解决方案就是使用标准的for循环。如果您有更好的方法,请随便回答。


其实我能理解为什么他们不会尝试支持这个功能 - 你需要检查删除的项是否是当前项,否则你可能会不必要地向后跳过(例如,如果检查只基于长度是否减少了一个之类的东西)。看起来实现起来太复杂了。 - Gus Hogg-Blake
5个回答

53

让我们看看为什么JavaScript会表现出这样的行为。根据ECMAScript标准规范中对于Array.prototype.forEach的说明,当你删除索引为1的元素时,索引为2的元素变成了索引为1的元素,而该对象中不存在索引为2的元素。

因此,JavaScript在对象中寻找元素2,但找不到,所以它跳过了函数调用。

这就是为什么你只看到了ab


实现这个功能的正确方法是使用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' ]

2
如果你需要对数组项进行任何更改(就像我在我的情况下一样 - 对象数组),以及过滤出项,最好使用reduce。+1 - dewd
1
return 语句之后,您无法将任何内容记录到控制台。 - phil294
@Blauhirn 不可以。 - Juan Biscaia
@JuanBiscaia 这就是我的观点。在上面的例子中,有一个日志记录语句返回之后。 - phil294
1
@Blauhirn 哦,好的,抱歉,我很匆忙,没有注意到你并没有提出问题,我的错。 - Juan Biscaia

26

一种可能的方式是使用array.slice(0)函数,它创建了一个数组的拷贝(克隆),因此迭代和删除操作是分离的。

然后唯一需要更改原始方法中使用的array.forEach的是将其改为array.slice(0).forEach,这样就可以正常工作:

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

使用forEach后,数组仅会包含ac

您可以在这里找到jsFiddle演示。


1
如果您使用lodash/underscore,您可以这样做:_.clone(array).forEach(function (item) { ... }) - Yeonho
2
这很有趣,但它涉及到每次迭代都要进行查找,尽管搜索的数组大小会减小。编写一个 for 循环并注意索引会更快。 - Matthias
1
forEach has an index already. no need for indexOf - Curcuma_
@Curcuma_ 在这种情况下使用 forEach 索引将不起作用,请自行尝试。 - yehsuf

6

可以像thefourtheye的回答中使用Array.prototype.filter这样的方法,这是一个不错的选择,但也可以用while循环来实现。例如:

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

while循环的结束块只有一个右花括号 " } ",而不是 " }); "。通过应用这些小改变,代码将成功编译。 - Avnish Patel
非常正确,@AvnishPatel,发现得好!已修复。 - nik10110

4
另一个可能性是使用array.reduceRight函数来避免跳过:
//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);
< p >在使用 reduceRight 方法后,数组只会包含 ac 两个元素。


如果按照这种方式使用,它会忽略最后一个位置的项目。累加器(定义为“_”)包含数组中的第一个对象,如果没有传递initialValue。您需要向array.reduceRight(callback, initialValue)传递第二个参数(initialValue)。 - Christopher
@Christopher 谢谢!如果没有提供初始值,则将使用并跳过数组中的最后一个元素。 - Elvinn

0
const array = ["a", "b", "c"];
let i = array.length;
while (i--) {
    if (array[i] === "b") {
       array.splice(i, 1);
       break;
    }
}
console.log(item);

2
虽然这段代码可能回答了问题,但是提供关于为什么和/或如何回答问题的额外上下文可以提高其长期价值。 - S.B

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