javascript - 为什么在 JavaScript 的 forEach 中删除元素会有奇怪的行为?

标签 javascript arrays

我试图根据另一个数组中不存在的索引从数组中删除一个单词,但当我使用拼接和过滤方法时,我观察到奇怪的行为。

谁能解释一下下面的场景吗? 为什么在这两种情况下都会发生这样的情况,即使同一个对象在迭代中被更改

文字

['一'、'二'、'三'、'四'、'五'、'六'、'七']

可移除文字

['四', '二', '八']

let words = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
let removedWords = ['four', 'two', 'eight'];

words.forEach((word) => {
  console.log(word);
  if (removedWords.includes(word)) {
    words = words.filter((removableWord) => removableWord !== word)
  }
});

/* Output */
//one
//two
//three
//four
//five
//six
//seven

let words = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
let removedWords = ['four', 'two', 'eight'];
words.forEach((word, index) => {
  console.log(word);
  if (removedWords.includes(word)) {
    words.splice(index, 1);
  }
});

/* Output */
//one
//two
//four
//six
//seven

正如本文中提到的Mozila document forEach() 在迭代之前不会复制数组。在过滤并分配回原始对象后,它的行为不应该与拼接相同吗?

注意:补充一下,splice 方法会对原始数组进行更改,而 filter 方法会创建数组的新副本,并且不会更改原始数组,但在给定的情况下例如(过滤后),结果被分配回原始数组。

最佳答案

您的第一个示例也有效,但您的 console.log 可能会让您感到困惑。在过滤之前,您在 for 循环中为每个单词记录一次。
只需在循环后记录结果 words 即可查看其是否有效。

let words = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
let removedWords = ['four', 'two', 'eight'];

words.forEach((word, index, iterationArray) => {
  console.log(index, word, iterationArray.length, words.length);
  if (removedWords.includes(word)) {
    words = words.filter((removableWord) => removableWord !== word)
  }
});

console.log(words);

对OP评论的回答:
所以我猜你很困惑

"forEach() does not make a copy of the array before iterating"

  • 确实,forEach() 不会创建副本:但是在循环内创建副本
  • 开头的变量 words 是对原始数组的引用['one', 'two', ' Three', 'four', ' Five', 'six' , '七']
  • 现在您调用 words.forEach() 这是一个返回此数组上的迭代器的函数(迭代器将始终指向此原始数组,无论您是否更改 引用点稍后)
    • 我已将第三个参数 iterationArray 添加到 forEach,这是迭代器使用的数组
    • 我还在循环内添加了一个 console.log:注意,iterationArray 不会改变,但 words.length 会改变更改(因为您为其分配了新数组)
  • 在循环中使用 words.filter 创建一个新数组(例如 ['one', 'two', ' Three', ' Five', 'six', 'seven '])并更改 words 变量以指向这个新数组 - 但这不会更改您之前已经创建的迭代器:即 forEach循环仍然“指向”原始数组

对于第二个示例:

let words = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
let removedWords = ['four', 'two', 'eight'];
words.forEach((word, index, iterationArray) => {
  console.log(word, index, iterationArray.length);
  if (removedWords.includes(word)) {
    words.splice(index, 1);
  }
});
console.log(words);

  • 同样,forEach 不会复制
  • 迭代器将指向原始数组
  • 但是现在您在循环内部更改了原始数组:即
    • 当循环到达索引 1 处的单词“two”时,将原始数组更改为 ['one', ' Three', 'four', ' Five', 'six', 'seven']
    • 换句话说:您删除了索引 1 处的项目,而其他项目则向左移动
  • 现在迭代器将继续,下一个索引是 2
    • 由于您更改了原始数组,索引 2 处的值现在为 four(并且您错过了单词 trhee,它现在位于迭代器的索引 1 处)已经处理完毕
  • 注意,循环内的 console.log:您可以看到 iterationArray 已更改

关于javascript - 为什么在 JavaScript 的 forEach 中删除元素会有奇怪的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73117942/

相关文章:

javascript - 循环遍历所有不是文件类型的输入项

javascript - Node.js 中所有内置事件的列表?

javascript - Ruby on Rails - AJAX 分页中的 JQuery 未运行

javascript - Dart 是真正的并行还是并发

javascript - 在两个不同的点击事件之间传递数组值

c++ - 将整数数组转换为数字

javascript - 是否有更简单的方法来推送到 JSON 数组或创建数组(如果它尚不存在)?

javascript - 如何在 v4.0 中向 select2 元素添加类

javascript - 我可以使用 reduce 将数组转换为具有自定义键并计算重复值的对象数组吗?

javascript - 使用 map 时使用扩展运算符扩展数组