前端进阶之旅前端进阶之旅
  • 基础篇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 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库Set 迭代中修改元素行为
JSJavaScript数组与集合

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

Set 不允许同时存在重复成员,但一次遍历仍可能多次访问一个被删除后重新加入的值。

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

根据变更时机判断可见性

  1. 重复添加已有成员不新增位置也不刷新顺序
  2. 访问前删除当前待处理成员被移除后跳过
  3. 删除后重插成为后来加入的位置,可能再次访问

示例用显式一次性标志限制重新加入,只演示有限重复。不要在真实服务中运行无上限的删除重插循环来验证风险。

核心回答

先记住这个答案

Set 按成员成功加入的顺序迭代,数组展开消费同样的默认顺序;对已存在值再次 add 不会移动位置。遍历结束前新增且在访问前未删除的成员可以被访问,未访问就被删除的成员会跳过,已访问成员若删除后重新加入则可能再次出现。因此集合唯一性不保证本轮回调每个业务值只执行一次,持续删除重插还可能让遍历无法结束。固定批次应先建立成员快照或把变更延迟到扫描后;动态工作队列则应明确重试、去重和总工作上限。

  • 初始顺序来自成功插入而不是排序
  • 删除重插会建立新的可访问位置
  • 唯一成员集合不等于一次遍历只处理一次

先区分重复 add 与删除重插

示例先创建 A、B,再次 add A 后展开仍是 A、B,因为已有成员并未移动。随后在 forEach 第一次看到 A 时删除并重新加入,A 成为末尾新位置,本轮又被访问一次。

只在首次执行分支里重插,使演示有确定终止条件。若每次遇到 A 都重复同样操作,就可能不断创造后续待访问位置,即使任意时刻 Set.size 都很小。

小集合也可能在一轮中重复访问JavaScript
const values = new Set(['A', 'B']);
values.add('A');
console.log(JSON.stringify([...values]));
const seen = [];
let requeued = false;
values.forEach(value => {
  seen.push(value);
  if (value === 'A' && !requeued) {
    requeued = true;
    values.delete('A');
    values.add('A');
  }
});
console.log(JSON.stringify(seen));
console.log(JSON.stringify([...values]));
查看输出与解释
["A","B"]
["A","B","A"]
["B","A"]

同一轮访问序列包含两次 A,但集合最终只有 B、A 两个成员。唯一性约束的是当前集合状态,遍历历史还受到删除和重新加入的时间顺序影响。

新成员和已删除成员按访问时机判断

如果在访问 A 时删除尚未访问的 B,B 不会因为最初在集合里就必然被调用;此时追加 C,则 C 可以在本轮结束前被访问。不能把 Set 当作开始时复制了固定条目数组。

同样,这些规则不应写成引擎随意决定是否访问。应明确新增发生在遍历完成前、成员是否仍存在及是否重新建立位置,才能从规范语义推导具体例子。

固定批次与动态队列需要不同组织方式

只想处理启动时的成员,可以先 [...set] 得到快照再遍历,并决定期间被取消的项目是否还应执行。快照固定了引用序列,但对象成员的内部状态仍可能变化,必要时还需要版本或取消检查。

如果确实允许回调追加后续工作,就应把它作为队列设计,限制重试次数和总任务量,并记录失败与取消。不要依赖一个 Set 同时隐式承担队列、正在处理和已完成三种状态。

回答前,多想一步

容易答错的地方

Set 没重复元素就认为不会重复调用
删除后重插建立新的遍历位置,已经处理过的值仍可能再次出现。需要一次处理语义时应保存明确完成状态或固定批次,不能把集合唯一性扩大成执行历史去重保证。
用 size 有限证明循环一定结束
不断移除再加入可能让待访问位置持续出现,而当前成员数量几乎不变。终止证明要看工作是否减少、是否存在重试上限,不能只看集合在每个时刻有多少成员。
试着用自己的话回答

面试官还会怎么问?

把 Set 展开成数组后的顺序稳定吗?

在没有干扰读取的修改时,结果遵循成员成功插入顺序,重复 add 已有值不改变位置。若输入构建顺序来自并发完成时机,展开只是忠实保留它,不会自动变成业务排序。

删除当前成员会让后面的成员跳过吗?

Set 不像数组 splice 那样把后续索引前移,遍历会继续处理符合规则的剩余成员。但后续成员是否存在仍受其他删除操作影响,应按集合条目的生命周期分析。

快照遍历就绝对没有并发问题了吗?

快照只固定当时的成员引用列表,成员对象状态和外部业务条件仍可变化。需要执行前校验、取消检查或版本匹配时,应明确加入这些条件,不能把浅快照当成事务隔离。

从一道题,走向一组知识

把知识连起来

数组与集合

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

比较数组固定初始长度与 Set 动态条目可见性的差别。

数组与集合

Map 的插入顺序怎样变化,和对象数字属性顺序有什么区别?

对照键值集合的更新与删除重插如何改变顺序。

参考资料

  • MDN:Set.forEach
  • MDN:Set

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

本题目录
  1. 先记住这个答案
  2. 先区分重复 add 与删除重插
  3. 新成员和已删除成员按访问时机判断
  4. 固定批次与动态队列需要不同组织方式
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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