proxy 本身不直接提供撤销功能,但可通过拦截 set/deleteproperty 等操作记录变更日志(如 {type, key, oldvalue, newvalue}),再通过 undo() 方法依栈顶记录反向恢复状态,并支持多级撤销、防误操作及封装为可复用工厂函数。

Proxy 本身不直接提供撤销功能,但可以结合历史快照或操作日志机制,实现对象状态的“可撤销更新”。核心思路是:用 Proxy 拦截修改操作,记录每次变更(如属性名、旧值、新值),再提供一个 undo() 方法回滚到最后一次变更。
拦截关键操作并保存变更日志
需要拦截 set、deleteProperty,以及可能的 defineProperty 等。每次成功修改时,把变更信息推入栈中。注意只记录实际发生的变化(比如新旧值不同才记),避免冗余。
- 用数组作为操作栈,每项是形如
{ type: 'set', key, oldValue, newValue }的对象 -
oldValue需在 set 前读取(可能是Reflect.get(target, key)),注意处理in判断和undefined边界 - 对已存在的属性赋相同值,或删除不存在的属性,可选择跳过记录
实现 undo 并还原上一次状态
undo() 从栈顶弹出一条记录,按类型反向执行:对 set 恢复 oldValue;对 deleteProperty 重新赋值;对新增属性则 delete 它。还原后需确保目标对象真实更新,且后续拦截仍生效。
- 还原时用
Reflect.set(target, key, oldValue)而非直接赋值,保持一致性 - 若还原的是删除操作,要先检查属性是否还存在(防止重复 restore)
- 建议在 undo 后触发一个事件或返回操作详情,方便调试和组合使用
支持多级撤销与防误操作
单层 undo 容易误用,可扩展为支持 undo(n) 回退 n 步,或加 canUndo 判断栈是否为空。同时考虑冻结历史快照——比如用户连续输入时,只在特定时机(如 blur 或 enter)才提交变更并入栈,避免高频操作撑爆内存。
- 用
Array.prototype.slice()或深拷贝保存快照,适用于复杂嵌套对象(但性能代价高) - 更轻量做法是只记录路径和值,配合
lodash.set/get动态操作,适合扁平对象 - 撤销后可自动清空后续历史(即“重做”失效),符合多数用户直觉
封装成可复用的函数
把逻辑收拢为工厂函数,接受原始对象和配置项(如是否启用深度监听、最大历史长度),返回带 proxy 和 undo 方法的对象。这样既保持 Proxy 的响应式能力,又对外隐藏实现细节。
- 返回对象可包含
history(只读数组)、clearHistory()、reset()等辅助方法 - 避免在 Proxy 内部直接修改 target,所有变更走 Reflect,保证可预测性
- 注意 Proxy 无法拦截原型链访问和某些不可枚举属性,这类变更不在撤销范围内
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











