JavaScript中forEach删除元素出现异常行为的原因解析
数组迭代中splice与filter赋值的行为差异解析
我尝试从一个数组中移除存在于另一个数组中的元素,但在使用splice和filter方法时观察到异常行为,想请教:为何两种情况会出现这样的表现,明明在迭代时修改的是同一个对象?
相关数组定义:
- Words数组:
['one', 'two', 'three', 'four', 'five', 'six', 'seven'] - 待移除单词数组:
['four', 'two', 'eight']
代码示例1:forEach结合filter赋值
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) } }); /* 输出 */ //one //two //three //four //five //six //seven
代码示例2:forEach结合splice
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); } }); /* 输出 */ //one //two //four //six //seven
我注意到MDN文档提到:forEach()在迭代前不会复制数组,但在过滤后将结果赋值回原变量后,它的表现为何与splice不一致?
补充说明:splice方法会修改原数组,而filter方法会创建新数组副本且不修改原数组,但在上述示例中,过滤后的结果已赋值回原数组。
行为差异解释
代码示例1的原理
forEach在启动迭代时,会绑定当前数组的引用,后续迭代完全基于这个原始数组的元素集合和长度执行,不会受变量重新赋值的影响。
当你执行words = words.filter(...)时,只是把words变量指向了一个全新的数组对象,但forEach迭代的依然是最初的那个原始数组。哪怕后续words指向了新数组,迭代过程还是会遍历原始数组的所有7个元素,所以控制台会输出全部单词。
代码示例2的原理
splice是直接修改原始数组本身(包括元素内容和数组长度),forEach迭代时会动态读取数组的当前状态,但迭代的索引是按初始数组的顺序依次推进的:
- 索引0:元素
one不匹配,输出one,数组无变化 - 索引1:元素
two匹配,执行splice(1,1),数组变为['one','three','four','five','six','seven'],长度从7变为6 - 索引2:此时原数组索引2的元素是
four(因为three前移到了索引1),输出four并执行splice(2,1),数组变为['one','three','five','six','seven'],长度变为5 - 索引3:当前数组索引3的元素是
six,输出six - 索引4:元素
seven,输出seven - 初始数组长度为7,但当前数组长度已变为5,索引5、6超出范围,迭代终止
这就导致迭代跳过了three和five,最终输出结果为one、two、four、six、seven。
核心差异总结
filter赋值:改变的是变量的引用,forEach绑定的是迭代启动时的原始数组,新数组对迭代过程无影响splice操作:直接修改原始数组的内容和长度,forEach迭代时会使用数组的当前状态,但索引按初始序列推进,从而出现元素跳过的情况
内容的提问来源于stack exchange,提问作者Dip Parmar
相关产品推荐
相关产品推荐

