在遍历 Set 时安全删除元素的核心是避免直接修改原 Set 的结构,以下是几种不影响遍历的删除方法,适用于不同场景:
一、先遍历收集待删除元素,再批量删除
核心思路
- 遍历 Set 时记录需要删除的元素到临时数组。
- 遍历结束后,批量删除临时数组中的元素。
示例代码
const set = new Set([1, 2, 3, 4, 5]);
const toDelete = [];
// 第一步:遍历并收集待删除元素
for (const item of set) {
if (item % 2 === 0) {
// 删除偶数
toDelete.push(item);
}
}
// 第二步:批量删除(不影响遍历结果)
toDelete.forEach(item => set.delete(item));
console.log([...set]); // 输出: [1, 3, 5]
优势
- 遍历过程中仅读取 Set,不修改其结构,避免迭代器异常。
- 批量删除效率高于单次删除,减少 DOM 操作或内存波动(若 Set 较大)。
二、转换为数组副本后遍历原 Set
核心思路
- 将 Set 转换为数组(创建副本)。
- 遍历数组副本,通过原 Set 的
delete()
方法删除元素。
示例代码
const set = new Set([1, 2, 3, 4, 5]);
// 转换为数组副本
[...set].forEach(item => {
if (item > 3) {
set.delete(item); // 删除原 Set 中的元素
}
});
console.log([...set]); // 输出: [1, 2, 3]
优势
- 遍历的是数组副本,原 Set 的删除操作不影响遍历顺序。
- 代码简洁,适用于删除逻辑简单的场景。
三、使用迭代器手动控制遍历与删除
核心思路
- 获取 Set 的迭代器,手动调用
next()
方法控制遍历。 - 删除元素后,通过重置迭代器或调整循环逻辑避免状态异常。
示例代码
const set = new Set([1, 2, 3, 4, 5]);
const iterator = set[Symbol.iterator]();
let current;
while ((current = iterator.next()) && !current.done) {
const item = current.value;
if (item % 2 === 0) {
set.delete(item);
// 注意:删除后迭代器可能指向已删除元素,需重新获取迭代器
iterator = set[Symbol.iterator](); // 重新获取迭代器
}
}
console.log([...set]); // 输出: [1, 3, 5]
注意事项
- 删除元素后必须重新获取迭代器,否则可能跳过后续元素。
- 此方法适用于复杂删除逻辑,但代码复杂度较高,需谨慎使用。
四、结合 WeakSet
或临时标记
核心思路
- 使用
WeakSet
或对象属性标记待删除元素。 - 遍历结束后,根据标记批量删除。
示例代码(使用 WeakSet 标记)
const set = new Set([{
id: 1 }, {
id: 2 }, {
id: 3 }]);
const toDelete = new WeakSet();
// 标记待删除元素
for (const item of set) {
if (item.id % 2 === 0) {
toDelete.add(item);
}
}
// 批量删除标记元素
for (const item of set) {
if (toDelete.has(item)) {
set.delete(item);
}
}
console.log([...set].map(item => item.id)); // 输出: [1, 3]
优势
- 适用于元素为对象的场景,通过引用标记避免值比较误差。
- 两次遍历分离逻辑,确保删除操作不影响第一次遍历的结果。
五、最佳实践总结
- 优先使用批量删除:方法一和方法二是最安全的方案,适用于 90% 以上的删除场景。
- 避免直接操作原 Set:任何在遍历过程中直接调用
set.delete()
的操作都可能引发异常,必须通过副本或临时存储解耦。 - 复杂场景的权衡:
- 若删除逻辑依赖遍历过程中的动态条件(如删除当前元素后影响后续判断),建议使用方法三手动控制迭代器,但需格外注意迭代器状态。
- 若元素为对象且需通过引用删除,优先使用
WeakSet
标记(方法四)。
- 性能优化:
- 当 Set 元素数量巨大时,方法一(临时数组)比方法二(数组转换)更节省内存,因为无需存储整个 Set 的副本。
六、常见错误与解决方案
错误操作 | 问题 | 解决方案 |
---|---|---|
在 for...of 中直接 set.delete(item) |
可能跳过元素或引发迭代器异常 | 改用方法一或方法二 |
删除元素后继续使用原迭代器 | 迭代器指向已删除元素,导致错误 | 删除后重新获取迭代器(方法三) |
依赖 for...of 遍历顺序删除元素 |
顺序可能因删除操作改变 | 先收集待删除项,再批量处理 |
通过以上方法,可在不影响遍历的前提下安全删除 Set 元素,确保程序逻辑的稳定性和可预测性。