undo/redo系统中set去重需用稳定键(如json序列化或哈希)标识快照,而非直接存对象;push前校验键是否已存在,确保逻辑相同的状态不重复入栈,并同步维护键集合与历史生命周期。

在 Undo/Redo 系统中使用 Set 去重无效快照,核心不是靠 Set 自动识别“语义重复”,而是用它配合**可比较的快照标识符**(如序列化后的字符串、结构哈希或版本指纹),过滤掉内容完全一致的历史节点。
快照必须可判定相等性
Set 默认用 SameValueZero 比较值 —— 对对象引用不敏感。直接把状态对象(如 { count: 1, list: ['a'] })塞进 Set,每次都是新对象,Set 认为全不同,无法去重。
解决办法:将快照转为**唯一、稳定、可比的键**:
- 用
JSON.stringify(state)(适合纯 JSON 可序列化状态,注意属性顺序、undefined、function、Date 等会被忽略或报错) - 用结构化克隆 + 生成 SHA-256 哈希(更健壮,需额外库如
crypto.subtle或第三方) - 手动提取关键字段拼接成规范字符串(例如
`${state.count}-${state.list.length}-${state.list.join('|')}`)
在 push 历史前检查是否重复
不是把所有快照存进 Set 再去遍历,而是在每次 push 新快照前,先算出它的标识键,查 Set 是否已存在:
const snapshotKeys = new Set();
const history = [];
function pushSnapshot(state) {
const key = JSON.stringify(state); // 或更稳妥的哈希/摘要
if (snapshotKeys.has(key)) return; // 重复,跳过
snapshotKeys.add(key);
history.push({ state, timestamp: Date.now() });
}
这样能避免把两个“逻辑相同但对象不同”的快照都记入历史,节省内存和还原时的冗余操作。
撤销/重做时保持快照键同步
Undo/Redo 往往涉及移动指针(如 currentIndex)。当执行 undo() 后又修改状态,新快照可能和之前某次完全一样 —— 此时仍要走上述 key 判重逻辑。
关键点:
-
snapshotKeys必须与history生命周期一致:清空历史时也要清空Set - 若支持“撤销后新操作清空 redo 栈”,那被删掉的 redo 快照对应
key也应从Set中删除(否则未来可能误判为重复) - 若快照含时间戳、随机 ID 等非状态字段,务必在生成
key前剔除,否则相同状态也会被当成不同
替代方案:用 Map 替代 Set 管理快照索引
如果还需快速查找某个状态是否出现过、或定位其最早/最近位置,可用 Map<key index></key>:
const keyToIndices = new Map();
function pushSnapshot(state) {
const key = stableKey(state);
const indices = keyToIndices.get(key) || [];
indices.push(history.length);
keyToIndices.set(key, indices);
history.push(state);
}
这比单纯去重更灵活,比如可限制同一状态最多保留一次,或自动合并相邻重复操作。
不复杂但容易忽略:Set 本身不会帮你理解“什么是重复”,你得定义清楚“什么才算同一个快照”,再把它翻译成计算机能比的值。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











