闭包是最轻量的快照捕获载体,通过封装操作瞬间的基础类型值、配合显式拷贝与双栈结构(history/redoStack),实现可靠Undo/Redo;需规避引用对象、循环变量共享,并支持持久化与长度限制。

闭包本身不生成快照,但它是最轻量、最自然的“快照捕获载体”——关键在于用它把操作发生那一瞬间的状态变量封存起来,形成不可变的执行上下文。真正可用的 Undo/Redo 系统,是靠闭包 + 显式拷贝 + 栈结构共同构建的。
闭包封装状态快照:捕获“那一刻”的值
每次用户触发有效操作(如点击“删除节点”、拖拽结束、表单提交),立即创建一个闭包,把当时需要还原的关键数据“锁住”:
- 只捕获原始值:ID、坐标、文本内容、枚举状态、布尔开关等基础类型,直接赋值进闭包作用域
- 避免引用对象:不闭包 *User 或 document.getElementById('x'),否则 Undo 时读到的是最新 DOM 或已修改对象
- 大对象深拷贝或存标识:图像、富文本块等不直接序列化进闭包,改存哈希、版本号或临时路径,还原时按需加载
- 循环中用 let 或 IIFE:防止所有闭包共享同一个循环变量,导致快照全部错位
命令对象结构:Do 和 Undo 两个闭包字段
不用接口、不抽象类,用一个结构体持有一对函数即可:
- Do:执行正向操作,内部已绑定目标 ID、新位置、插入内容等
- Undo:执行反向操作,内部已绑定旧值、原坐标、删除前索引等——这些值在创建命令时就确定,不是运行时再去查
- 例如移动小球:Do 设置
el.style.left = '200px';Undo 回设el.style.left = '100px',其中'100px'是创建命令时就捕获的 oldPos
栈管理:用 slice 实现线性历史与重做暂存
撤销重做本质是时间线上的前进与后退,必须用可索引、可弹出的顺序容器:
- history []Command:存放所有已执行且未被撤销的命令,末尾为最新操作
- redoStack []Command:Undo 时从 history 弹出的命令,暂存于此等待 Redo
- 新命令执行前,清空 redoStack——用户做了新操作,旧重做路径即失效
- Undo:从 history pop 最后一项 → 执行其 Undo() → push 到 redoStack
- Redo:从 redoStack pop 最后一项 → 执行其 Do() → push 到 history
防错与持久化:内存快照不够,还得落盘
仅靠闭包+内存栈无法应对刷新、关闭标签页等场景:
- 快照生成后,同步写入 localStorage 或 IndexedDB,键名建议含会话 ID 或时间戳
- 页面加载时检查是否有待还原快照,有则自动恢复并清除存储,避免重复执行
- 限制历史长度(如最多 50 条),超出时 FIFO 清理最老项,防止内存持续增长
- 对不可逆操作(如发邮件、调用支付接口)标记 CanUndo: false,或设计伪可逆逻辑(如发撤回请求)

















