迭代期间新增元素时循环次数差异:forEach与for/of行为原因求解
forEach 与 for...of 迭代新增元素行为差异的底层原因
你观察到的行为是ECMAScript标准明确规定的正常表现,两者底层执行逻辑的差异导致了处理迭代过程中数组变动的区别。
复现代码
let my_array1 = [1,2,3] let my_array2 = ["a", "b", "c"] // 不会动态新增迭代周期,循环将按预定义的次数执行结束 my_array1.forEach((value, index, the_array) => { if (value == 3){ the_array.push(3) // 这种写法效果一致 } }) console.log(`my_array1 after iteration ${my_array1}`) // 会动态新增迭代周期,将触发无限循环!!! for (let value of my_array2){ if (value == "c"){ my_array2.push("c") } } console.log(`my_array2 after iteration ${my_array2}`)
Array.prototype.forEach 的执行规则
根据ES规范,forEach 调用时会优先执行以下两步:
- 先获取当前数组的
length属性值,作为固定的迭代次数上限存储下来,后续迭代过程中不会再更新该值 - 按照索引从
0到「提前存储的length值-1」的顺序依次调用回调函数,哪怕迭代过程中数组新增了元素、length变大,超出原始长度的索引也不会被遍历到
如果迭代过程中数组元素被删除,对应索引位置找不到有效值时,回调会跳过该次执行。
for...of 的执行规则
for...of 循环的本质是调用遍历对象的迭代器(Iterator)接口,数组的默认迭代器执行逻辑为:
- 初始化当前遍历索引为
0 - 每次迭代时调用迭代器的
next()方法,此时才会判断当前索引是否小于数组实时的length值- 是:返回当前索引对应的值,索引自增1,进入下一轮迭代
- 否:返回
done: true,结束循环
因此你在迭代到值为"c"的元素时持续往数组末尾新增元素,数组的实时length会不断变大,迭代器的索引永远达不到大于等于length的阈值,就会触发无限循环。
额外补充
普通的for(let i = 0; i < arr.length; i++)循环和for...of表现一致,因为每次循环判断条件时都会读取数组最新的length值;如果需要避免for...of的动态遍历特性,可以提前对数组做浅拷贝再遍历,比如for (let value of [...my_array2]),此时迭代的是固定长度的拷贝数组,不会受原数组变动影响。
内容的提问来源于stack exchange,提问作者RomanCanada
相关产品推荐
相关产品推荐

