前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库forEach map 修改原数组行为
JSJavaScript数组与集合

forEach 或 map 遍历时修改原数组,哪些变化会被看到?

数组迭代方法固定的主要是开始时的长度上界,不是把所有元素值都拍成了快照。

前端进阶之旅 · 一题精讲更新于 2026.09.06
JavaScript#数组与集合
先看核心答案读代码示例
理解线索

对每次变化问三个问题

  1. 是否在上界内原长度之外不属于本轮扫描范围
  2. 是否已经访问过去的索引不会因为改值自动重跑
  3. 访问时存在吗删除与补入会改变属性检查结果

这里只讨论指定数组回调方法的扫描规则。for...of 的数组迭代器和 Set 的遍历有不同机制,不能直接套用同一长度快照结论。

核心回答

先记住这个答案

forEach、map 等方法开始时读取长度作为扫描上界,但访问每个索引时仍检查属性是否存在并读取当时的值。因此追加到原长度之外的元素通常不会被本轮处理,未访问位置改值会影响后续回调,访问前已删除且无继承同名属性的位置会被跳过。原范围内原本空缺的位置若在访问前补上,也可能被访问;不能说所有新增元素都被忽略。这些是可推导的语义,不应笼统归因于引擎随机差异。复杂业务最好把扫描与结构变更分开,明确固定批次还是动态工作队列。

  • 初始长度上界不是完整内容快照
  • 未访问索引读取的是当时存在的属性和值
  • 新增是否可见取决于位置与发生时机

把追加、删除和改值放在首次回调里

示例开始长度三,处理 A 时追加 D、删除索引一并更新索引二。后续跳过已删除的一,读取改写后的 C,而新增 D 超出初始上界,不会被此次 forEach 访问。

输出同时保留访问索引和最终长度,避免只看元素文本误以为 D 没有被成功添加。是否写入数组与是否属于当前扫描范围,是两件可以同时成立而结果不同的事。

初始上界与后续实时读取JavaScript
const values = ['A', 'B', 'C'];
const seen = [];
values.forEach((value, index) => {
  seen.push(index + ':' + value);
  if (index === 0) {
    values.push('D');
    delete values[1];
    values[2] = 'C*';
  }
});
console.log(JSON.stringify(seen));
console.log(values.length, values[3], Object.hasOwn(values, 1));
查看输出与解释
["0:A","2:C*"]
4 D false

D 已存在于数组中但不在本轮原始扫描范围,一号位置在访问前被删除,二号位置读取新值。结果可以按固定上界与逐索引属性读取推导,不需要诉诸不确定执行顺序。

原范围内补入空位与尾部追加不同

如果原数组长度三且一号位置为空,在处理零号时补入一号,它仍位于尚未访问的原范围内,因此之后可以被处理。候选题中“新增一律跳过”的概括遗漏了位置这一关键条件。

相反,修改已经访问的零号不会让回调重新开始。业务如果希望新增工作立即被处理,应使用明确队列和终止条件,不能把数组回调方法当成隐式动态任务调度器。

删除导致前移时要检查是否跳项

splice 删除当前项会让后续元素向前移动,而扫描索引仍继续增加,于是刚移到当前位置的元素可能被跳过。这与 delete 留下空位的行为不同,排查时需要确认具体修改方法。

可选择先生成删除计划、用 filter 构造新数组或针对索引设计倒序操作。复制数组也只固定外层引用序列,对象内部仍可能被修改;是否需要更深快照取决于正在保护的业务一致性。

回答前,多想一步

容易答错的地方

认为保存初始长度就保存了全部值
回调读取未访问位置时仍能看到改值或删除,因此整个数组并未被冻结。需要稳定输入批次时应明确复制与版本边界,不能用长度快照推导数据值不变。
把所有新增元素都归为不会访问
尾部追加到原范围外与在原范围内补上缺失索引不同。应结合索引是否已访问、是否在原上界内以及访问时属性是否存在逐步判断,避免背一个过度简化的结论。
试着用自己的话回答

面试官还会怎么问?

map 的结果长度会跟着追加增长吗?

普通 map 按开始时读取的长度创建对应结果范围,原数组后来追加的尾部项不会自动扩展本轮映射结果。未被访问的缺失索引还可能保留结果空位,应按具体属性变化检查。

修改对象元素字段能被后面看到吗?

数组保存对象引用,其他位置或外部代码若持有同一对象,就可能看到字段变化。固定数组长度或浅复制并不隔离对象内部状态,副作用分析需要继续追踪引用身份。

能把遍历中修改说成未定义行为吗?

不能一概这样说,这些方法对长度、属性检查和访问时机有明确规则。复杂修改容易难读,工程上可以避免,但应区分不推荐的写法与语言没有定义的行为。

从一道题,走向一组知识

把知识连起来

数组与集合

Set 遍历时新增、删除和重新加入成员,会不会重复处理?

对照 Set 新增与删除重插为什么可能再次被访问。

数组与集合

map、filter、sort 链式处理会创建几份数组,怎样减少中间分配?

用生成新结果分离读取与变更,并评估额外分配。

参考资料

  • MDN:Array 迭代方法
  • MDN:forEach
  • MDN:map

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 把追加、删除和改值放在首次回调里
  3. 原范围内补入空位与尾部追加不同
  4. 删除导致前移时要检查是否跳项
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑